Class: Hue::Hue

Inherits:
Object
  • Object
show all
Defined in:
lib/hue.rb

Overview

Hue is a class that can interact with and control a Philips Hue base station.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Hue

Hue can be initialized with a Hash of options.

  • opts should contain the IP of the base station

    by default, Hue will use discover_ip to attempt to find it.
    
  • opts is a User-Agent-like string, describing this client.

    defaults to 'ruby-hue'
    
  • opts is a hex secret string used for authentication

    defaults to the SHA1 hex digest of the hostname
    


45
46
47
48
49
# File 'lib/hue.rb', line 45

def initialize(opts = {})
  @ip = opts[:ip] || self.class.discover_ip
  @client = opts[:client] || 'ruby-hue'
  @username = opts[:username] || Digest::SHA1.hexdigest(`hostname`.strip)
end

Class Method Details

.discover_ipObject

Hue.discover_ip is a convenience class method that will scan the network and attempt to find the Philips base station. It may take ~5s to execute.



19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/hue.rb', line 19

def self.discover_ip
  UPnP::SSDP.log = false # get rid of this pesky debug logging!
  services = UPnP::SSDP.search('urn:schemas-upnp-org:device:basic:1').map {|s| s[:location] }
  valid_location = services.find do |l|
    xml = Curl.get(l).body_str
    doc = Nokogiri::XML(xml)
    name = doc.css('friendlyName').first
    return false unless name
    name.text =~ /^Philips hue/
  end
  raise 'no hue found on this network' unless valid_location
  /(([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])/.match(valid_location)[0]
end

Instance Method Details

#all_lightsObject

Hue#all_lights is awesome. It returns a ‘HueAllLightsProxy` object, which is a simple object that will forward any method calls it gets to this instance of Hue, once for each light, inserting the light id as the first argument.

Imagine we had the following variables:

hue = Hue.new
proxy = hue.all_lights

The following method call:

proxy.write hue: 0, bri: 200

Would essentially be translated to these three method calls (for 3 lights):

hue.write 1, hue: 0, bri: 200
hue.write 2, hue: 0, bri: 200
hue.write 3, hue: 0, bri: 200

Typically, you would simply use this inline, like this:

hue.all_lights.set_bright_color(Color::RGB::Blue)


135
136
137
# File 'lib/hue.rb', line 135

def all_lights
  HueAllLightsProxy.new(self)
end

#authorizeObject

Hue#authorize authorizes this client (@client + @username combination) with the base station forever. It’s a one-time operation.

#authorize requires manual interation: the method must be called, after which somebody must physically press the button on the base station. Once that is done, the method must be called again and, this time, a success confirmation message will be returned.



72
73
74
# File 'lib/hue.rb', line 72

def authorize
  _request(:post, "http://#{@ip}/api/", devicetype: @client, username: @username)
end

#each_lightObject

Like Enum’s ‘each`, Hue#each_light takes a block which is called for each light id. For example:

hue.each_light do |l|
  hue.set_bright_color(l, Color::RGB::Blue)
end


103
104
105
106
# File 'lib/hue.rb', line 103

def each_light
  lights.each {|k,v| yield k }
  nil
end

#lightsObject

Hue#lights returns lights from the cached state, from the poll_state method above. The state of the light themselves is only as current as the last poll_state method call.



90
91
92
93
# File 'lib/hue.rb', line 90

def lights
  poll_state unless @state
  @state['lights']
end

#off(light) ⇒ Object

Hue#off turns off the specified light.



173
174
175
# File 'lib/hue.rb', line 173

def off(light)
  write(light, on: false)
end

#on(light) ⇒ Object

Hue#on turns on the specified light.



180
181
182
# File 'lib/hue.rb', line 180

def on(light)
  write(light, on: true)
end

#poll_stateObject

Hue#poll_state returns the entire state of the system.



79
80
81
82
83
# File 'lib/hue.rb', line 79

def poll_state
  state = request(:get, '/')
  raise unless state['lights'] # poor man's way of checking for success
  @state = state
end

#presetObject

Hue#preset returns a HuePresetsProxy object, which is meant to be used much like Hue#all_lights. For example:

hue.preset.cycle_thru_colors


219
220
221
# File 'lib/hue.rb', line 219

def preset
  HuePresetsProxy.new(self)
end

#request(method, path, body = {}) ⇒ Object

Hue#request is makes an authorized request to the Hue backend, relative to the url ‘@ip/api/@user`. Include any leading slashes.

This method is mostly used internally, but made avaiable publicly for shits and giggles.



58
59
60
61
# File 'lib/hue.rb', line 58

def request(method, path, body = {})
  url = "http://#{@ip}/api/#{@username}#{path}"
  _request(method, url, body)
end

#set_bright_color(light, color, opts = {}) ⇒ Object

Hue#set_bright_color is often more useful than its ‘set_color` counterpart, because when converting RGB colors to HSL, a color might not be expected to have full brightness. But with a known hue & saturation, it’s often desirable to have full brightness. This method uses ‘bri: 255`



207
208
209
210
211
# File 'lib/hue.rb', line 207

def set_bright_color(light, color, opts={})
  color = color.to_hsl
  color.l = 1
  set_color(light, color, opts)
end

#set_color(light, color, opts = {}) ⇒ Object

Hue#set_color sets a light to a specified color.

set_color expects color to be a color object from the ‘color` gem, or something that implements its interface:

color must implement to_hsl, which must return an object that implements .h, .l, and .s. These methods are expected to return floats in (0.0..1.1)



193
194
195
196
197
198
199
# File 'lib/hue.rb', line 193

def set_color(light, color, opts={})
  hsl = color.to_hsl
  opts = opts.merge(bri: (hsl.l * 255).to_i,
                    sat: (hsl.s * 255).to_i,
                    hue: (hsl.h * 360 * 182).to_i)
  write(light, opts)
end

#write(light, state) ⇒ Object

Hue#write is the meat of what Hue is all about. This is the method that writes a state change to a Hue bulb.

In order to avoid being rate limited at the HTTP api level, this method implements its own rate limiting in which it will block and wait for enough time to elapse when messages are fired too quickly.

State is an object which typically contains a combination of these keys:

:hue            => this is a hue value in the range (0..65535)
                   can be derived from (degrees * 182)
:bri            => "brightness" in the range (0..255)
:sat            => "saturation" in the range (0..255)
:alert          => when set, triggers alert behavior. options:
                   :select
                      triggers a flash to the given color, instead of
                      a permanent change, when set to `true`
                   :lselect
                      same as above, except this is a constant flashing,
                      insead of a one time trigger
:transitiontime => an Integer representing the "transition time," ie. the
                   amount of time over which the color fade will occur.
                   this is measured in tenths of seconds. a value of
                   0 means a hard switch, no fade. defaulys to ~5-ish


165
166
167
168
# File 'lib/hue.rb', line 165

def write(light, state)
  wait_for_rate_limit
  request(:put, "/lights/#{light}/state", state)
end