Module: NLHue::Disco

Defined in:
lib/nlhue/disco.rb

Overview

Use #start_discovery and #stop_discovery for continuous bridge discovery. Use #send_discovery to perform discovery only once.

Constant Summary collapse

MAX_BRIDGE_AGE =

Number of times a bridge can be missing from discovery if not subscribed (approx. 5*15=1.25min if interval is 15)

5
MAX_SUBSCRIBED_AGE =

Number of times a bridge can be missing from discovery if subscribed (approximately one month if interval is 15). This number is higher than MAX_BRIDGE_AGE because update failures (see MAX_BRIDGE_ERR) will detect an offline bridge, and working bridges sometimes disappear from discovery.

150000
MAX_BRIDGE_ERR =

Number of times a bridge can fail to update

4
@@bridges =

Bridges discovered on the network

serial

> {

:bridge => NLHue::Bridge, :age => [# of failed discos], :errcount => [# of failed updates] }

{}
@@disco_interval =
15
@@disco_timer =
nil
@@disco_proc =
nil
@@disco_done_timer =
nil
@@disco_callbacks =
[]
@@bridges_changed =
false
@@disco_running =
false
@@disco_connection =
nil

Class Method Summary collapse

Class Method Details

.add_disco_callback(cb = nil, &block) ⇒ Object

Adds the given block to be called with discovery events. The return value may be passed to remove_disco_callback.

The callback will be called with the following events: :start - A discovery process has started. :add, bridge - The given bridge was recently discovered. :del, bridge, msg - The given bridge was recently removed because of [msg]. May be called even if no discovery process is running. :end, true/false - A discovery process has ended, and whether there were changes to the list of bridges.



140
141
142
143
144
145
# File 'lib/nlhue/disco.rb', line 140

def self.add_disco_callback cb=nil, &block
	raise 'No callback was given.' unless block_given? || cb
	raise 'Pass only a block or a Proc object, not both.' if block_given? && cb
	@@disco_callbacks << (cb || block)
	block
end

.bridgesObject

Returns an array of the bridges previously discovered on the network.



169
170
171
172
173
# File 'lib/nlhue/disco.rb', line 169

def self.bridges
	@@bridges.map do |serial, info|
		info[:bridge]
	end
end

.disco_running?Boolean

Indicates whether a discovery process is currently running.

Returns:

  • (Boolean)


163
164
165
# File 'lib/nlhue/disco.rb', line 163

def self.disco_running?
	@@disco_running
end

.disco_started?Boolean

Indicates whether #start_discovery has been called.

Returns:

  • (Boolean)


125
126
127
# File 'lib/nlhue/disco.rb', line 125

def self.disco_started?
	!!(@@disco_timer || @@disco_running)
end

.do_discoObject

Triggers a discovery process immediately (fails if #start_discovery has not been called), unless discovery is already in progress.



154
155
156
157
158
159
160
# File 'lib/nlhue/disco.rb', line 154

def self.do_disco
	raise 'Call start_discovery() first.' unless @@disco_proc
	return if @@disco_running

	@@disco_timer.cancel if @@disco_timer
	@@disco_proc.call if @@disco_proc
end

.get_bridge(serial) ⇒ Object

Returns a bridge with the given serial number, if present.



176
177
178
179
# File 'lib/nlhue/disco.rb', line 176

def self.get_bridge serial
	serial &&= serial.downcase
	@@bridges[serial] && @@bridges[serial][:bridge]
end

.get_error_count(serial) ⇒ Object

Gets the number of consecutive times a bridge has failed to update. This value is only updated if the Bridge object’s subscribe or update methods are called.



191
192
193
194
# File 'lib/nlhue/disco.rb', line 191

def self.get_error_count serial
	serial &&= serial.downcase
	@@bridges[serial] && @@bridges[serial][:errcount]
end

.get_missing_count(serial) ⇒ Object

Gets the number of consecutive times a bridge has been missing from discovery.



183
184
185
186
# File 'lib/nlhue/disco.rb', line 183

def self.get_missing_count serial
	serial &&= serial.downcase
	@@bridges[serial] && @@bridges[serial][:age]
end

.remove_disco_callback(callback) ⇒ Object

Removes a discovery callback (call this with the return value from add_disco_callback)



148
149
150
# File 'lib/nlhue/disco.rb', line 148

def self.remove_disco_callback callback
	@@disco_callbacks.delete callback
end

.send_discovery(timeout = 4, &block) ⇒ Object

Sends an SSDP discovery request, then yields an NLHue::Bridge object for each Hue hub found on the network. Responses may come for more than timeout seconds after this function is called. Reuses Bridge objects from previously discovered bridges, if any. Returns the connection used by SSDP.



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/nlhue/disco.rb', line 201

def self.send_discovery timeout=4, &block
	# Even though we put 'upnp:rootdevice' in the ST: header, the
	# Hue hub sends multiple matching and non-matching responses.
	# We'll use a hash to track which IP addresses we've seen.
	devs = {}

	con = NLHue::SSDP.discover 'upnp:rootdevice', timeout do |ssdp|
		if ssdp && ssdp['Location'].include?('description.xml') && ssdp['USN']
			serial = ssdp['USN'].gsub(/.*([0-9A-Fa-f]{12}).*/, '\1')
			unless devs.include?(ssdp.ip) && devs[ssdp.ip].serial == serial
				dev = @@bridges.include?(serial) ?
					@@bridges[serial][:bridge] :
					Bridge.new(ssdp.ip, serial)

				dev.addr = ssdp.ip unless dev.addr == ssdp.ip

				if dev.verified?
					yield dev
				else
					dev.verify do |result|
						if result && !con.closed?
							devs[ssdp.ip] = dev
							begin
								yield dev
							rescue => e
								log_e e, "Error notifying block with discovered bridge #{serial}"
							end
						end
					end
				end
			end
		end
	end

	con
end

.start_discovery(username = nil, interval = 15) ⇒ Object

Starts a timer that periodically discovers Hue bridges. If the username is a String or a Hash mapping bridge serial numbers (String or Symbol) to usernames, the Bridge objects’ username will be set before trying to update.

Using very short intervals may overload Hue bridges, causing light and group commands to be delayed or erratic.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/nlhue/disco.rb', line 52

def self.start_discovery username=nil, interval=15
	raise 'Discovery is already running' if @@disco_timer || @@disco_running
	raise 'Interval must be a number' unless interval.is_a? Numeric
	raise 'Interval must be >= 1' unless interval >= 1

	unless username.nil? || username.is_a?(String) || username.is_a?(Hash)
		raise 'Username must be nil, a String, or a Hash'
	end

	@@disco_interval = interval

	@@disco_proc = proc {
		@@disco_timer = nil
		@@disco_running = true

		notify_callbacks :start

		@@bridges.each do |k, v|
			v[:age] += 1
		end

		reset_disco_timer nil, 5
		@@disco_connection = send_discovery do |br|
			if br.is_a?(NLHue::Bridge) && disco_started?
				if @@bridges.include?(br.serial) && br.subscribed?
					reset_disco_timer br
				else
					u = lookup_username(br, username)
					br.username = u if u
					br.update do |status, result|
						if disco_started?
							reset_disco_timer br
						elsif !@@bridges.include?(br.serial)
							# Ignore bridges if disco was shut down
							br.clean
						end
					end
				end
			end
		end
	}

	do_disco
end

.stop_discoveryObject

Stops periodic Hue bridge discovery and clears the list of bridges. Preserves disco callbacks.



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/nlhue/disco.rb', line 99

def self.stop_discovery
	@@disco_timer.cancel if @@disco_timer
	@@disco_done_timer.cancel if @@disco_done_timer
	@@disco_connection.shutdown if @@disco_connection

	bridges = @@bridges.clone
	bridges.each do |serial, info|
		puts "Removing bridge #{serial} when stopping discovery" # XXX
		@@bridges.delete serial
		notify_callbacks :del, info[:bridge], 'Stopping Hue Bridge discovery.'
		info[:bridge].clean
	end
	if @@disco_running
		@@disco_running = false
		notify_callbacks :end, !bridges.empty?
	end

	EM.next_tick do
		@@disco_timer = nil
		@@disco_done_timer = nil
		@@disco_proc = nil
		@@disco_connection = nil
	end
end