hued 0.0.1

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: 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: []