Module: NLHue::Disco

Extended by:
Log
Includes:
Log
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

Methods included from Log

bench, log, log_e, on_bench, on_log, on_log_e

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.



143
144
145
146
147
148
# File 'lib/nlhue/disco.rb', line 143

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.



172
173
174
175
176
# File 'lib/nlhue/disco.rb', line 172

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)


166
167
168
# File 'lib/nlhue/disco.rb', line 166

def self.disco_running?
	@@disco_running
end

.disco_started?Boolean

Indicates whether #start_discovery has been called.

Returns:

  • (Boolean)


128
129
130
# File 'lib/nlhue/disco.rb', line 128

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.



157
158
159
160
161
162
163
# File 'lib/nlhue/disco.rb', line 157

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.



179
180
181
182
# File 'lib/nlhue/disco.rb', line 179

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.



194
195
196
197
# File 'lib/nlhue/disco.rb', line 194

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.



186
187
188
189
# File 'lib/nlhue/disco.rb', line 186

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)



151
152
153
# File 'lib/nlhue/disco.rb', line 151

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.



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
237
238
239
# File 'lib/nlhue/disco.rb', line 204

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.



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
96
97
98
# File 'lib/nlhue/disco.rb', line 55

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.



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

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