hued 0.0.1

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: b5cf08955db23b65793e437c07db14d6b866017b
4
+ data.tar.gz: 086a26303c15748e74ed137ab8d44e23f25b9590
5
+ SHA512:
6
+ metadata.gz: d5a4c0d3238d1278a878fc5053f1aa5f6601a00a9f15bfde35c53597d97124a1bc8ea7b2c13a11ad4cfaea34cced002d4e2509acce8494d01eb588b501bfb215
7
+ data.tar.gz: 1785b51a324ec14fe8e1237177013a217440a583d102f7de2c4797e9eb6fc335a2396c758feb53a0e4222faa2c51b9ec7b8c215830ac35edbef6fd43c2654886
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ ruby '2.0.0'
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'json', '~> 1.8.3', '>= 1.8'
6
+
7
+ group :development do
8
+ gem 'jeweler', '~> 2.0.1', '>= 2.0.1'
9
+ end
@@ -0,0 +1,57 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ addressable (2.4.0)
5
+ builder (3.2.2)
6
+ descendants_tracker (0.0.4)
7
+ thread_safe (~> 0.3, >= 0.3.1)
8
+ faraday (0.9.2)
9
+ multipart-post (>= 1.2, < 3)
10
+ git (1.3.0)
11
+ github_api (0.13.1)
12
+ addressable (~> 2.4.0)
13
+ descendants_tracker (~> 0.0.4)
14
+ faraday (~> 0.8, < 0.10)
15
+ hashie (>= 3.4)
16
+ multi_json (>= 1.7.5, < 2.0)
17
+ oauth2
18
+ hashie (3.4.4)
19
+ highline (1.7.8)
20
+ jeweler (2.0.1)
21
+ builder
22
+ bundler (>= 1.0)
23
+ git (>= 1.2.5)
24
+ github_api
25
+ highline (>= 1.6.15)
26
+ nokogiri (>= 1.5.10)
27
+ rake
28
+ rdoc
29
+ json (1.8.3)
30
+ jwt (1.5.1)
31
+ mini_portile2 (2.0.0)
32
+ multi_json (1.11.3)
33
+ multi_xml (0.5.5)
34
+ multipart-post (2.0.0)
35
+ nokogiri (1.6.7.2)
36
+ mini_portile2 (~> 2.0.0.rc2)
37
+ oauth2 (1.1.0)
38
+ faraday (>= 0.8, < 0.10)
39
+ jwt (~> 1.0, < 1.5.2)
40
+ multi_json (~> 1.3)
41
+ multi_xml (~> 0.5)
42
+ rack (>= 1.2, < 3)
43
+ rack (1.6.4)
44
+ rake (10.5.0)
45
+ rdoc (4.2.2)
46
+ json (~> 1.4)
47
+ thread_safe (0.3.5)
48
+
49
+ PLATFORMS
50
+ ruby
51
+
52
+ DEPENDENCIES
53
+ jeweler (~> 2.0.1, >= 2.0.1)
54
+ json (~> 1.8.3, >= 1.8)
55
+
56
+ BUNDLED WITH
57
+ 1.11.2
@@ -0,0 +1,149 @@
1
+ hued - (?:ab)using the Hue HTTP API
2
+ ====
3
+
4
+ - [writeup](#writeup)
5
+ - [API methods](#api-methods)
6
+ - [notes](#notes)
7
+
8
+ # writeup
9
+
10
+ want to talk to your [Philips Hue](http://www2.meethue.com/en-us/) lights directly through an HTTP API without registering an application?
11
+
12
+ to turn off the currently-in-use lighting scheme:
13
+
14
+ ```
15
+ ~/hue $ curl -X PUT http://<hue hub>/api/<token>/groups/0/action -d '{"on":true}'
16
+ [{"success":{"/groups/0/action/on":true}}]
17
+ ```
18
+
19
+ all you need are:
20
+ - the IP for the Hue Hub plugged in to your network
21
+ - a 'whitelisted' token to talk to the API
22
+
23
+ finding the IP should be pretty straight forward, but the nmap output is not very specific:
24
+ ```
25
+ ~/hue $ nmap 192.168.42.0/24
26
+ ...
27
+ Nmap scan report for 192.168.42.66
28
+ Host is up (0.0063s latency).
29
+ Not shown: 65534 closed ports
30
+ PORT STATE SERVICE VERSION
31
+ 80/tcp open tcpwrapped
32
+ ...
33
+ ```
34
+
35
+ the Hue hub uses DHCP by default, so it likely won't be at that address for you, but you get the idea.
36
+
37
+ now, you need to get a token. to do that, trick the Hue app on your phone/tablet/Echo to send it to us.
38
+
39
+ - stand up a webserver listening for GET of `http://0.0.0.0:80/api/config` on the same network (0/24) your phone/tablet/Echo is on
40
+ - `api/config` needs to be JSON that a Hue hub would return (sample included in repo)
41
+ - from your phone/tablet/Echo, select `Settings->Find Bridge->Search`
42
+ * this works intermittently, as some times the app found the real hub and the imposter, other times it would only find the real hub, but would mostly end up with a 'Specify IP' button
43
+ - wait for the app to query your imposter, and you'll have a token
44
+
45
+ by the numbers:
46
+
47
+ ```
48
+ ~/hue $ cat api/config
49
+ HTTP/1.1 200 OK
50
+ Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
51
+ Pragma: no-cache
52
+ Expires: Mon, 1 Aug 2011 09:00:00 GMT
53
+ Connection: close
54
+ Access-Control-Max-Age: 3600
55
+ Access-Control-Allow-Origin: *
56
+ Access-Control-Allow-Credentials: true
57
+ Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE, HEAD
58
+ Access-Control-Allow-Headers: Content-Type
59
+ Content-type: application/json
60
+
61
+ {"name": "Philips hue","swversion": "01032318","apiversion": "1.13.0","mac": "DE:AD:BE:EF:CA:FE","bridgeid": "001788FFFECAFE","factorynew": false,"replacesbridgeid": null,"modelid": "BSB001"}
62
+ ~/hue $ while true; do sudo nc -l 80 < api/config; done
63
+ ...
64
+ GET /api/config HTTP/1.1
65
+ Host: 192.168.42.83
66
+ Accept: */*
67
+ Accept-Language: en-us
68
+ Connection: keep-alive
69
+ Accept-Encoding: gzip, deflate
70
+ User-Agent: Hue/1 CFNetwork/758.4.3 Darwin/15.5.0
71
+
72
+ GET /api/eKpsfhR9K1u32/config HTTP/1.1
73
+ Host: 192.168.42.83
74
+ Accept: */*
75
+ Accept-Language: en-us
76
+ Connection: keep-alive
77
+ Accept-Encoding: gzip, deflate
78
+ User-Agent: Hue/1 CFNetwork/758.4.3 Darwin/15.5.0
79
+
80
+ GET /api/eKpsfhR9K1u32 HTTP/1.1
81
+ Host: 192.168.42.83
82
+ Accept: */*
83
+ Accept-Language: en-us
84
+ Connection: keep-alive
85
+ Accept-Encoding: gzip, deflate
86
+ User-Agent: Hue/1 CFNetwork/758.4.3 Darwin/15.5.0
87
+ ```
88
+
89
+ and now we have our token, `eKpsfhR9K1u32`. with that, we can call (all?) API methods
90
+
91
+ # API methods
92
+
93
+ api|description|GET|PUT
94
+ ----|-----------|---------------|----------------
95
+ `/config/`|set and query existing settings|without token for unauthenticated, basic registration information, with token for light/device/schedule/sensor configuration|JSON matching schema validation|
96
+ `/lights/`|scan and query existing lights|JSON scan status|empty body to start a scan
97
+ `/sensors/`|scan and query existing sensors|JSON scan status|empty body to start a scan
98
+ `/scenes/`|set and query existing scenes|JSON scene list| /`<uuid>/lights/<id>/state => {"on":true,"xy":[0.5804,0.3995],"bri":253}`
99
+ `/schedules/`|set and query existing schedules/timers|JSON schedules/timers|`/<uuid> => {"name":"Alarm","autodelete":false,"localtime":"2016-06-20T16:20:00","description":"giants","status":"enabled","command":{"address":"/api/eKpsfhR9K1u32/groups/0/action","body":{"scene":"f55e38250-on-0"},"method":"PUT"}}`
100
+ `/groups/`|set and query scene (?) groupings|empty JSON|`/<id>/action => {"scene":"2fc89fcdb-on-0"}`
101
+
102
+ a few example request/responses:
103
+
104
+ ```json
105
+ # http://192.168.42.66/api/eKpsfhR9K1u32/scenes
106
+ {
107
+ "f4750b0cf-off-5": {
108
+ "name": "HIDDEN foff 1452936620159",
109
+ "lights": [
110
+ "1",
111
+ "2",
112
+ "3",
113
+ "4",
114
+ "5",
115
+ "6",
116
+ "7",
117
+ "8",
118
+ "9",
119
+ "10"
120
+ ],
121
+ "owner": "eKpsfhR9K1u32",
122
+ "recycle": true,
123
+ "locked": true,
124
+ "appdata": {
125
+
126
+ },
127
+ "picture": "",
128
+ "lastupdated": "2016-01-16T09:30:21",
129
+ "version": 1
130
+ },
131
+ ...
132
+ }
133
+ ```
134
+
135
+ # notes
136
+
137
+ all versions tested are the latest available as of 2016/06/19
138
+
139
+ component|version|notes
140
+ ---------|-------|-----
141
+ Philips Hue Hub|5.23.1.13452|
142
+ Hue mobile App|1.12.1.0|same version reported on both Android and Apple devices
143
+
144
+ TODO
145
+ * dig further in api/\<token\>/config
146
+ * determine how the hashes are generated. not concatenation of create time / name in any obvious way. different devices seem to come up with hashes in different ways. older iPhone/iPad apps were [A-Z0-9]{16}, while Android ones seem to always have been [A-Z]{32}
147
+ * write a client library/binding? or at least some abstraction
148
+ * determine the truly necessary pieces of the imposter response, think it really only needs content-type and minimal JSON
149
+ * work on SSDP discovery, see [description.xml](resources/api/description.xml)
@@ -0,0 +1,19 @@
1
+ require 'jeweler'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+
6
+ CLEAN.include('pkg/*')
7
+
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = 'hued'
10
+ gem.summary = 'Philips Hue HTTP API bindings'
11
+ gem.description = 'interact with Philips Hue Hub to control your devices'
12
+ gem.email = ['conor.code@gmail.com']
13
+ gem.homepage = 'http://github.com/chorankates/hued'
14
+ gem.authors = ['Conor Horan-Kates', 'Maureen Long']
15
+ gem.licenses = 'MIT'
16
+
17
+ gem.executables = ['hued']
18
+ end
19
+ Jeweler::RubygemsDotOrgTasks.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ # hued - CLI for Hued module
3
+
4
+ require 'hued'
5
+
6
+ def parse_input
7
+ options = Hash.new
8
+
9
+ parser = OptionParser.new do |o|
10
+ o.on('--ip <ip>', 'IP address of Phillips Hue Hub') do |p|
11
+ options[:ip] = p
12
+ end
13
+ o.on('--token <token>', 'whitelisted token') do |p|
14
+ options[:token] = p
15
+ end
16
+ end
17
+
18
+ parser.parse!
19
+ options
20
+ end
21
+
22
+ options = parse_input
23
+
24
+ hub = Hued::Hub.new(options[:ip], options[:token])
25
+
26
+ # TODO this should really be a REPL
27
+ off = hub.all_lights_off
28
+ puts "off: #{off}"
29
+
30
+ sleep 5
31
+
32
+ on = hub.all_lights_on
33
+ puts "on: #{on}"
34
+
35
+ off = hub.all_lights_off
36
+ puts "off(2): #{off}"
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env ruby
2
+ ## hued.rb Philips Hue HTTP binding
3
+
4
+ require 'json'
5
+ require 'net/http'
6
+ require 'optparse'
7
+ require 'yaml'
8
+ require 'uri'
9
+
10
+ module Hued
11
+
12
+ class Hub
13
+
14
+ attr_accessor :lights, :scenes, :schedules
15
+
16
+ def initialize(ip, token)
17
+ @ip = ip
18
+ @token = token
19
+
20
+ raise "no IP specified" if @ip.nil?
21
+ raise "no token specified" if @token.nil?
22
+
23
+ @config = get_config()
24
+
25
+ @lights = get_lights()
26
+ @scenes = get_scenes()
27
+ @schedules = get_schedules()
28
+ end
29
+
30
+ def inspect
31
+ {
32
+ :ip => @ip,
33
+ :token => @token,
34
+ :lights => @lights.size,
35
+ :scenes => @scenes.size,
36
+ :schedules => @schedules.size,
37
+ }
38
+ end
39
+
40
+ def to_s
41
+ inspect.to_s
42
+ end
43
+
44
+ def get_config
45
+ JSON.parse(get_http(get_url('config')).body)
46
+ end
47
+
48
+ def get_lights(input = nil)
49
+ lights = Array.new
50
+ response = JSON.parse(get_http(get_url('lights')).body)
51
+ response.each do |_i, hash|
52
+ lights << OpenStruct.new(hash)
53
+ end
54
+
55
+ lights
56
+ end
57
+
58
+ def get_scenes(input = nil)
59
+ scenes = Array.new
60
+ response = JSON.parse(get_http(get_url('scenes')).body)
61
+ response.each do |_i, hash|
62
+ scenes << OpenStruct.new(hash)
63
+ end
64
+
65
+ scenes
66
+ end
67
+
68
+ def get_schedules(input = nil)
69
+ schedules = Array.new
70
+ response = JSON.parse(get_http(get_url('schedules')).body)
71
+ response.each do |_i, hash|
72
+ schedules << OpenStruct.new(hash)
73
+ end
74
+
75
+ schedules
76
+ end
77
+
78
+ def all_lights_on
79
+ url = get_url('groups/0/action')
80
+ payload = { :on => true }
81
+ put_http(url, payload)
82
+ end
83
+
84
+ def all_lights_off
85
+ url = get_url('groups/0/action')
86
+ payload = { :on => false }
87
+ put_http(url, payload)
88
+ end
89
+
90
+ ## helper functions
91
+ def get_url(method)
92
+ sprintf('http://%s/api/%s/%s', @ip, @token, method)
93
+ end
94
+
95
+ def get_http(url)
96
+ uri = URI.parse(url)
97
+ http = Net::HTTP.new(uri.host, uri.port)
98
+ http.use_ssl = false
99
+ request = Net::HTTP::Get.new(uri.request_uri)
100
+ http.request(request)
101
+ end
102
+
103
+ def put_http(url, body)
104
+ uri = URI.parse(url)
105
+ http = Net::HTTP.new(uri.host, uri.port)
106
+ request = Net::HTTP::Put.new(uri.request_uri)
107
+
108
+ request.add_field('Content-Type', 'application.json')
109
+ request.body = body.to_json
110
+ http.request(request)
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -0,0 +1,13 @@
1
+ HTTP/1.1 200 OK
2
+ Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
3
+ Pragma: no-cache
4
+ Expires: Mon, 1 Aug 2011 09:00:00 GMT
5
+ Connection: close
6
+ Access-Control-Max-Age: 3600
7
+ Access-Control-Allow-Origin: *
8
+ Access-Control-Allow-Credentials: true
9
+ Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE, HEAD
10
+ Access-Control-Allow-Headers: Content-Type
11
+ Content-type: application/json
12
+
13
+ {"name": "Philips hue","swversion": "01032318","apiversion": "1.13.0","mac": "DE:AD:BE:EF:CA:FE","bridgeid": "001788FFFECAFE","factorynew": false,"replacesbridgeid": null,"modelid": "BSB001"}
@@ -0,0 +1,39 @@
1
+
2
+
3
+ <?xml version="1.0" encoding="UTF-8"?>
4
+ <root xmlns="urn:schemas-upnp-org:device-1-0">
5
+ <specVersion>
6
+ <major>1</major>
7
+ <minor>0</minor>
8
+ </specVersion>
9
+ <URLBase>http://192.168.42.66:80/</URLBase>
10
+ <device>
11
+ <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>
12
+ <friendlyName>Philips hue (192.168.42.66)</friendlyName>
13
+ <manufacturer>Royal Philips Electronics</manufacturer>
14
+ <manufacturerURL>http://www.philips.com</manufacturerURL>
15
+ <modelDescription>Philips hue Personal Wireless Lighting</modelDescription>
16
+ <modelName>Philips hue bridge 2012</modelName>
17
+ <modelNumber>000</modelNumber>
18
+ <modelURL>http://www.meethue.com</modelURL>
19
+ <serialNumber>000</serialNumber>
20
+ <UDN>000</UDN>
21
+ <presentationURL>index.html</presentationURL>
22
+ <iconList>
23
+ <icon>
24
+ <mimetype>image/png</mimetype>
25
+ <height>48</height>
26
+ <width>48</width>
27
+ <depth>24</depth>
28
+ <url>hue_logo_0.png</url>
29
+ </icon>
30
+ <icon>
31
+ <mimetype>image/png</mimetype>
32
+ <height>120</height>
33
+ <width>120</width>
34
+ <depth>24</depth>
35
+ <url>hue_logo_3.png</url>
36
+ </icon>
37
+ </iconList>
38
+ </device>
39
+ </root>
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hued
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Conor Horan-Kates
8
+ - Maureen Long
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-06-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '1.8'
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.3
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '1.8'
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.3
34
+ - !ruby/object:Gem::Dependency
35
+ name: jeweler
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 2.0.1
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.0.1
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - '>='
49
+ - !ruby/object:Gem::Version
50
+ version: 2.0.1
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.0.1
54
+ description: interact with Philips Hue Hub to control your devices
55
+ email:
56
+ - conor.code@gmail.com
57
+ executables:
58
+ - hued
59
+ extensions: []
60
+ extra_rdoc_files:
61
+ - README.md
62
+ files:
63
+ - Gemfile
64
+ - Gemfile.lock
65
+ - README.md
66
+ - Rakefile
67
+ - VERSION
68
+ - bin/hued
69
+ - lib/hued.rb
70
+ - resources/api/config
71
+ - resources/api/description.xml
72
+ homepage: http://github.com/chorankates/hued
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.2.2
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Philips Hue HTTP API bindings
96
+ test_files: []