Class: NL::KndClient::EMKndClient

Inherits:
EM::Connection
  • Object
show all
Includes:
EM::Protocols::LineText2
Defined in:
lib/nl/knd_client/em_knd_client.rb

Overview

An asynchronous EventMachine-based client for KND, with full support for all major KND features.

There should only be a single global instance of this class at any given time (TODO: move class-level variables to instance variables, if possible with EventMachine).

Constant Summary collapse

DEPTH_SIZE =
640 * 480 * 11 / 8
VIDEO_SIZE =
640 * 480
BLANK_IMAGE =
NL::FastPng.store_png(640, 480, 8, "\x00" * (640 * 480))
@@logger =
->(msg) { puts "#{Time.now.iso8601(6)} - #{msg}" }
@@bencher =
nil
@@debug_cmd =
nil
@@zones =
{}
@@images =
{
  :depth => BLANK_IMAGE,
  :linear => BLANK_IMAGE,
  :ovh => BLANK_IMAGE,
  :side => BLANK_IMAGE,
  :front => BLANK_IMAGE,
  :video => BLANK_IMAGE
}
@@connect_cb =
nil
@@connection =
0
@@connected =
false
@@instance =
nil
@@fps =
0
@@occupied =
0
@@connection_time =

The time of the last connection/disconnection event

Time.now

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeEMKndClient

Returns a new instance of EMKndClient.



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/nl/knd_client/em_knd_client.rb', line 186

def initialize
  super
  @@connection = @@connection + 1
  @thiscon = @@connection
  @binary = :none
  @quit = false
  @commands = []
  @active_command = nil
  @@connected ||= false
  @tcp_ok = false
  @tcp_connected = false

  @getbright_sent = false # Whether a getbright command is in the queue
  @depth_sent = false # Whether a getdepth command is in the queue
  @video_sent = false # Whether a getvideo command is in the queue
  @requests = {
    :depth => [],
    :linear => [],
    :ovh => [],
    :side => [],
    :front => [],
    :video => []
  }

  # The Mutex isn't necessary if all signaling takes place on the event loop
  @image_lock = Mutex.new

  # Zone/status update callbacks (for protocol plugins like xAP)
  @cbs = []
end

Class Method Details

.bench(name, &block) ⇒ Object

Some EMKndClient functions call this method to wrap named sections of code with optional instrumentation. Use the .on_bench method to enable benchmarking/instrumentation.



70
71
72
73
74
75
76
# File 'lib/nl/knd_client/em_knd_client.rb', line 70

def self.bench(name, &block)
  if @@bencher
    @@bencher.call(name, block)
  else
    yield
  end
end

.blank_imageObject



84
85
86
# File 'lib/nl/knd_client/em_knd_client.rb', line 84

def self.blank_image
  BLANK_IMAGE
end

.clear_imagesObject



116
117
118
119
120
# File 'lib/nl/knd_client/em_knd_client.rb', line 116

def self.clear_images
  @@images.each_key do |k|
    @@images[k] = BLANK_IMAGE
  end
end

.connect(hostname) ⇒ Object

Changes the current hostname and opens the connection loop. If connection fails, it will be retried automatically, so this should only be called once for the application.



145
146
147
148
149
150
151
152
153
# File 'lib/nl/knd_client/em_knd_client.rb', line 145

def self.connect(hostname)
  self.hostname = hostname || '127.0.0.1'
  begin
    EM.connect(self.hostname, 14308, EMKndClient)
  rescue => e
    log e, 'Error resolving KND.'
    raise e
  end
end

.connected?Boolean

Whether the client is connected to the knd server

Returns:

  • (Boolean)


123
124
125
# File 'lib/nl/knd_client/em_knd_client.rb', line 123

def self.connected?
  @@connected || false
end

.debug_cmd?(cmdname) ⇒ Boolean

Returns true if the given command should have debugging info logged.

Returns:

  • (Boolean)


38
39
40
41
# File 'lib/nl/knd_client/em_knd_client.rb', line 38

def self.debug_cmd?(cmdname)
  init_debug_cmd if @@debug_cmd.nil?
	@@debug_cmd == true || (@@debug_cmd.is_a?(Array) && @@debug_cmd.include?(cmdname))
end

.fpsObject



168
169
170
# File 'lib/nl/knd_client/em_knd_client.rb', line 168

def self.fps
  @@fps
end

.hostnameObject



133
134
135
# File 'lib/nl/knd_client/em_knd_client.rb', line 133

def self.hostname
  @@hostname
end

.hostname=(name) ⇒ Object



137
138
139
140
# File 'lib/nl/knd_client/em_knd_client.rb', line 137

def self.hostname= name
  @@instance.close_connection_after_writing if @@connected
  @@hostname = name
end

.init_debug_cmdObject

Reads command debugging configuration from the KNC_DEBUG_CMD environment variable. Called automatically by .debug_cmd?.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/nl/knd_client/em_knd_client.rb', line 19

def self.init_debug_cmd
  dbg = ENV['KNC_DEBUG_CMD']

  case dbg
  when 'true'
    @@debug_cmd = true

  when 'false'
    @@debug_cmd = false

  when String
    @@debug_cmd = dbg.split(',').map(&:strip)

  else
    @@debug_cmd = false
  end
end

.instanceObject

The currently-connected client instance.



156
157
158
# File 'lib/nl/knd_client/em_knd_client.rb', line 156

def self.instance
  @@instance if @@connected
end

.log(msg) ⇒ Object

Logs a message to STDOUT, or to the logging function specified by .on_log.

The KNC project this code was extracted from was written without awareness of the Ruby Logger class.



55
56
57
# File 'lib/nl/knd_client/em_knd_client.rb', line 55

def self.log(msg)
  @@logger.call(msg) if @@logger
end

.occupiedObject



164
165
166
# File 'lib/nl/knd_client/em_knd_client.rb', line 164

def self.occupied
  @@occupied
end

.on_bench(&block) ⇒ Object

Calls the given block for certain named sections of code. The benchmark block must accept a name parameter and a proc parameter, and call the proc. Disables EMKndClient benchmarking if no block is given. This is used by KNC to instrument



63
64
65
# File 'lib/nl/knd_client/em_knd_client.rb', line 63

def self.on_bench(&block)
  @@bencher = block
end

.on_connect(&bl) ⇒ Object

Sets or replaces a proc called with true or false when a connection is made or lost.



129
130
131
# File 'lib/nl/knd_client/em_knd_client.rb', line 129

def self.on_connect &bl
  @@connect_cb = bl
end

.on_log(&block) ⇒ Object

Sets a block to be called for any log messages generated by EMKndClient and related classes. The default is to print messages to STDOUT with a timestamp. Calling without a block will disable logging.



46
47
48
# File 'lib/nl/knd_client/em_knd_client.rb', line 46

def self.on_log(&block)
  @@logger = block
end

.png_data(type) ⇒ Object

Returns the most recent PNG of the given type, an empty string if no data, or nil if an invalid type.



112
113
114
# File 'lib/nl/knd_client/em_knd_client.rb', line 112

def self.png_data type
  @@images[type]
end

.zonesObject



160
161
162
# File 'lib/nl/knd_client/em_knd_client.rb', line 160

def self.zones
  @@zones
end

Instance Method Details

#add_cb(block) ⇒ Object

Adds a callback to be called when a zone is added, removed, or changed, the framerate changes, or the system disconnects.

For zone updates, the given block/proc/lambda will be called with the type of operation (:add, :del, :change) and the Zone object.

For status updates, the block/proc/lambda will be called with the status value being updated (:online, :fps) the value (true/false for :online, 0-30 for :fps), and for :online, the number of seconds since the last online/offline transition.

The block will be called with :fps and :add events as soon as it is added, and an :online event if already online.



871
872
873
874
875
876
877
878
879
880
881
# File 'lib/nl/knd_client/em_knd_client.rb', line 871

def add_cb block
  raise 'Parameter must be callable' unless block.respond_to? :call
  unless @cbs.include? block
    @cbs << block
    block.call :online, @@connected, (Time.now - @@connection_time) if @@fps > 0
    block.call :fps, @@fps
    @@zones.each do |k, v|
      block.call :add, v
    end
  end
end

#add_zone(zone, &block) ⇒ Object

Adds a new zone. Calls block with true and a message for success, false and a message for error.



719
720
721
722
723
724
725
726
727
728
729
730
731
# File 'lib/nl/knd_client/em_knd_client.rb', line 719

def add_zone zone, &block
  zone['name'] ||= 'New_Zone'
  zone['name'].gsub!(/ +/, '_')
  zone['name'].gsub!(/[^A-Za-z0-9_]/, '')

  if zone['name'].downcase == '__status'
    block.call false, 'Cannot use "__status" for a zone name.'
  else
    add_zone2(zone['name'], zone['xmin'], zone['ymin'], zone['zmin'], zone['xmax'], zone['ymax'], zone['zmax']) {|*args|
      block.call *args if block != nil
    }
  end
end

#add_zone2(name, xmin, ymin, zmin, xmax, ymax, zmax, &block) ⇒ Object

Adds a new zone. Calls block with true and a message for success, false and a message for error.



735
736
737
738
739
740
741
742
743
744
745
746
747
748
# File 'lib/nl/knd_client/em_knd_client.rb', line 735

def add_zone2 name, xmin, ymin, zmin, xmax, ymax, zmax, &block
  cmd = EMKndCommand.new 'addzone', name, xmin, ymin, zmin, xmax, ymax, zmax

  if block != nil
    cmd.callback { |cmd|
      block.call true, cmd.message
    }
    cmd.errback { |cmd|
      block.call false, (cmd ? cmd.message : 'timeout')
    }
  end

  do_command cmd
end

#check_requests(type) ⇒ Object

Returns true if there are requests pending of the given type



812
813
814
815
816
817
818
819
820
# File 'lib/nl/knd_client/em_knd_client.rb', line 812

def check_requests type
  ret = nil
  EMKndClient.bench("check_requests #{type}") do
    @image_lock.synchronize do
      ret = !@requests[type].empty?
    end
  end
  return ret
end

#clear_zones(&block) ⇒ Object



765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
# File 'lib/nl/knd_client/em_knd_client.rb', line 765

def clear_zones &block
  cmd = EMKndCommand.new 'clear'

  if block != nil
    cmd.callback { |cmd|
      @@zones.clear
      block.call true, cmd.message
    }
    cmd.errback { |cmd|
      block.call false, (cmd ? cmd.message : 'timeout')
    }
  end

  do_command cmd
end

#connection_completedObject



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/nl/knd_client/em_knd_client.rb', line 217

def connection_completed
  @tcp_connected = true

  log "Connected to depth camera server at #{EMKndClient.hostname} (connection #{@thiscon})."

  fps_proc = proc {
    do_command('fps') {|cmd|
      fps = cmd.message.to_i()
      if !@@connected && fps > 0
        log "Depth camera server is online (connection #{@thiscon})."

        @@connected = true
        @@connect_cb.call true if @@connect_cb

        now = Time.now
        elapsed = now - @@connection_time
        @@connection_time = now
        call_cbs :online, true, elapsed
      end

      @@fps = fps
      call_cbs :fps, @@fps

      @fpstimer = EM::Timer.new(0.3333333) do
        fps_proc.call
      end
    }.errback {|cmd|
      if cmd == nil
        log "FPS command timed out.  Disconnecting from depth camera server (connection #{@thiscon})."
      else
        log "FPS command failed: #{cmd.message}.  Disconnecting from depth camera server (connection #{@thiscon})."
      end
      close_connection
    }
  }
  zone_proc = proc {
    get_zones {
      # TODO: Unsubscribe and defer zones.json response response when
      # there is no web activity and xAP is off.
      @zonetimer = EM::Timer.new(2) do
        zone_proc.call
      end
    }
  }

  startup_proc = proc do
    fps_proc.call
    zone_proc.call
    subscribe().errback { |cmd|
      if cmd == nil
        log "Subscribe command timed out.  Disconnecting from depth camera server (connection #{@thiscon})."
      else
        log "Subscribe command failed: #{cmd.message}.  Disconnecting from depth camera server (connection #{@thiscon})."
      end
      close_connection
    }
  end

  do_command('ver') { |cmd|
    @version = cmd.message.split(' ', 2).last.to_i if cmd.message
    @fpstimer = EM::Timer.new(0.1, &startup_proc)
    log "Protocol version is #{@version}."
  }.errback { |cmd|
    if cmd == nil
      log "Version command timed out.  Disconnecting (connection #{@thiscon})."
      close_connection
    else
      log "Version command failed.  Assuming version 1."
      @version = 1
      @fpstimer = EM::Timer.new(0.1, &startup_proc)
    end
  }

  @@instance = self
rescue => e
  log e
  Kernel.exit
end

#do_command(command, &block) ⇒ Object

Pass a string or a EMKndCommand, returns the EMKndCommand. The block, if any, will be used as a EMKndCommand success callback

Most of the time you should use a more specific method, e.g. #add_zone, #clear_zones, etc.



537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
# File 'lib/nl/knd_client/em_knd_client.rb', line 537

def do_command command, &block
  if command.is_a? EMKndCommand
    cmd = command
  else
    cmd = EMKndCommand.new command
  end

  if block != nil
    cmd.callback do |*args|
      block.call *args
    end
  end

  log "do_command #{cmd.to_s}" if EMKndClient.debug_cmd?(cmd.name)
  send_data "#{cmd.to_s}\n"
  @commands << cmd
  cmd
end

#enter_depthObject



172
173
174
175
# File 'lib/nl/knd_client/em_knd_client.rb', line 172

def enter_depth
  @binary = :depth
  set_binary_mode(DEPTH_SIZE)
end

#enter_video(length = VIDEO_SIZE) ⇒ Object



177
178
179
180
# File 'lib/nl/knd_client/em_knd_client.rb', line 177

def enter_video(length = VIDEO_SIZE)
  @binary = :video
  set_binary_mode(length)
end

#get_image(type, &block) ⇒ Object

The parameter is the type of image to request (:depth, :linear, :ovh, :side, :front, or :video). The given block will be called with a string containing the corresponding PNG data, or an empty string.



840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
# File 'lib/nl/knd_client/em_knd_client.rb', line 840

def get_image type, &block
  raise "Invalid image type: #{type}" if @requests[type] == nil

  @image_lock.synchronize do
    if @requests[type].empty?
      request_image type
    end
    if block_given?
      @requests[type].push block
    else
      @requests[type].push proc { }
    end

    length = @requests[type].length
    log "There are now #{length} #{type} requests." if @@bencher && length > 1
  end
end

#get_zones(&block) ⇒ Object

Defers an update of the zone list. This will run the zones command to see if any zones have been removed. The block will be called with no parameters on success, if a block is given.



564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
# File 'lib/nl/knd_client/em_knd_client.rb', line 564

def get_zones &block
  # No subscription received after this zone command finishes can
  # contain a zone that was removed prior to the execution of
  # this command
  do_command 'zones' do |cmd|
    EMKndClient.bench('get_zones') do
      old_zones = @@zones
      zonelist = {}
      cmd.lines.each do |line|
        zone = Zone.new line
        oldzone = @@zones[zone['name']]
        zone['bright'] = oldzone['bright'] if oldzone && oldzone.include?('bright')
        zonelist[zone['name']] = zone
      end

      @@zones = zonelist

      # Notify protocol plugin callbacks about new zones
      EMKndClient.bench('get_zones_callbacks') do
        unless @cbs.empty?
          zonelist.each do |k, v|
            if !old_zones.include? k
              log "Zone #{k} added in get_zones"
              call_cbs :add, v
            elsif v['occupied'] != @@zones[k]['occupied']
              log "Zone #{k} changed in get_zones"
              call_cbs :change, v
            end
          end
          old_zones.each do |k, v|
            if !zonelist.include? k
              log "Zone #{k} removed in get_zones"
              call_cbs :del, v
            end
          end
        end
      end

      @@occupied = cmd.message.gsub(/.*, ([0-9]+) occupied.*/, '\1').to_i if cmd.message
    end

    if block != nil
      block.call
    end
  end
end

#leave_binaryObject



182
183
184
# File 'lib/nl/knd_client/em_knd_client.rb', line 182

def leave_binary
  @binary = :none
end

#receive_binary_data(d) ⇒ Object



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/nl/knd_client/em_knd_client.rb', line 378

def receive_binary_data(d)
  case @binary
  when :depth
    EM.defer do
      data = d
      begin
        @image_lock.synchronize do
          @depth_sent = false
        end

        unpacked = nil

        if(check_requests(:front) or check_requests(:ovh) or check_requests(:depth) or
            check_requests(:side) or check_requests(:linear))
          EMKndClient.bench('unpack') do
            unpacked = Kinutils.unpack11_to_16(data)
          end
        else
          raise "---- Received an unneeded depth image"
        end

        if check_requests(:depth)
          EMKndClient.bench('16png') do
            set_image :depth, NL::FastPng.store_png(640, 480, 16, unpacked)
          end
        end

        if check_requests(:linear)
          linbuf = nil
          EMKndClient.bench('linear_plot') do
            linbuf = Kinutils.plot_linear(unpacked)
          end
          EMKndClient.bench('linear_png') do
            set_image :linear, NL::FastPng.store_png(640, 480, 8, linbuf)
          end
        end

        if check_requests(:ovh)
          ovhbuf = nil
          EMKndClient.bench('ovh_plot') do
            ovhbuf = Kinutils.plot_overhead(unpacked)
          end
          EMKndClient.bench('ovh_png') do
            set_image :ovh, NL::FastPng.store_png(KNC_XPIX, KNC_ZPIX, 8, ovhbuf)
          end
        end

        if check_requests(:side)
          sidebuf = nil
          EMKndClient.bench('side_plot') do
            sidebuf = Kinutils.plot_side(unpacked)
          end
          EMKndClient.bench('side_png') do
            set_image :side, NL::FastPng.store_png(KNC_ZPIX, KNC_YPIX, 8, sidebuf)
          end
        end

        if check_requests(:front)
          frontbuf = nil
          EMKndClient.bench('front_plot') do
            frontbuf = Kinutils.plot_front(unpacked)
          end
          EMKndClient.bench('front_png') do
            set_image :front, NL::FastPng.store_png(KNC_XPIX, KNC_YPIX, 8, frontbuf)
          end
        end
      rescue => e
        log "Error in depth image processing task: #{e.to_s}"
        log "\t#{e.backtrace.join("\n\t")}"
      end
    end

  when :video
    EM.defer do
      data = d
      begin
        @image_lock.synchronize do
          @video_sent = false
        end

        unpacked = nil

        unless check_requests(:video)
          raise "---- Received an unneeded video image"
        end

        if d.bytesize != VIDEO_SIZE
          set_image :video, BLANK_IMAGE
          raise "---- Unknown video image format with size #{d.bytesize}; expected #{VIDEO_SIZE}"
        end

        EMKndClient.bench('videopng') do
          set_image :video, NL::FastPng.store_png(640, 480, 8, data)
        end

      rescue => e
        log "Error in video image processing task: #{e.to_s}"
        log "\t#{e.backtrace.join("\n\t")}"
      end
    end
  end

  leave_binary
end

#receive_line(data) ⇒ Object



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/nl/knd_client/em_knd_client.rb', line 296

def receive_line(data)
  # Send lines to any command waiting for data
  if @active_command
    @active_command = nil if @active_command.add_line data
    return
  end

  type, message = data.split(" - ", 2)

  case type
  when "DEPTH"
    enter_depth

  when 'VIDEO'
    length = message.gsub(/[^0-9 ]+/, '').to_i
    enter_video length

  when 'BRIGHT'
    line = message.kin_kvp
    name = Zone.fix_name! line['name']
    if @@zones.has_key? name
      match = @@zones[name]
      match['bright'] = line['bright'].to_i
      call_cbs :change, match
    else
      log "=== NOTICE - BRIGHT line for missing zone #{name}"
    end

  when "SUB"
    zone = Zone.new(message)
    name = zone["name"]
    if !@@zones.has_key? name
      log "=== NOTICE - SUB added zone #{name} ==="
      @@zones[name] = zone
      call_cbs :add, zone
    else
      match = @@zones[name]
      match.merge_zone zone
      call_cbs :change, match
    end

  when "ADD"
    zone = Zone.new(message)
    name = zone["name"]
    @@zones[name] = zone
    log "Zone #{name} added via ADD"
    call_cbs :add, zone

  when "DEL"
    name = message
    log "Zone #{name} removed via DEL"
    if @@zones.include? message
      zone = @@zones[message]
      @@zones.delete message
      call_cbs :del, zone
    else
      puts "=== ERROR - DEL received for nonexistent zone ==="
    end

  when "OK"
    if @commands.length == 0
      puts "=== ERROR - OK when no command was queued - disconnecting ==="
      close_connection
    else
      cmd = @commands.shift
      active = cmd.ok_line message
      @active_command = cmd unless active
    end

  when "ERR"
    if @commands.length == 0
      puts "=== ERROR - ERR when no command was queued - disconnecting ==="
      close_connection
    end
    @commands.shift.err_line message

  else
    puts "----- Unknown Response -----"
    p data
  end
end

#remove_cb(block) ⇒ Object

Removes a zone callback previously added with add_zone_cb. The block will be called with :online, false when it is removed.



885
886
887
888
889
# File 'lib/nl/knd_client/em_knd_client.rb', line 885

def remove_cb block
  raise 'Parameter must be callable' unless block.respond_to? :call
  @cbs.delete block
  block.call :online, false, (Time.now - @@connection_time)
end

#remove_zone(name, &block) ⇒ Object



750
751
752
753
754
755
756
757
758
759
760
761
762
763
# File 'lib/nl/knd_client/em_knd_client.rb', line 750

def remove_zone name, &block
  cmd = EMKndCommand.new 'rmzone', name

  if block != nil
    cmd.callback { |cmd|
      block.call true, cmd.message
    }
    cmd.errback { |cmd|
      block.call false, (cmd ? cmd.message : 'timeout')
    }
  end

  do_command cmd
end

#request_brightness(&block) ⇒ Object



781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
# File 'lib/nl/knd_client/em_knd_client.rb', line 781

def request_brightness &block
  return if @getbright_sent

  cmd = EMKndCommand.new 'getbright'

  cmd.callback { |cmd|
    block.call true, cmd.message if block
    @getbright_sent = false
  }
  cmd.errback { |cmd|
    block.call false, (cmd ? cmd.message : 'timeout') if block
    @getbright_sent = false
  }

  @getbright_sent = true
  do_command cmd
end

#set_zone(zone, separator = "\n", &block) ⇒ Object

Updates parameters on the given zone. The zone argument should be a Zone with only the changed parameters and zone name filled in. The changes will be merged with the existing zone data. If xmin, ymin, zmin, xmax, ymax, and zmax are all specified, then any other attributes will be ignored. Attributes will be set in the order they are returned by iterating over the keys in zone. The block, if specified, will be called with true and a message for success, false and a message for error. If multiple parameters are set, messages from individual commands will be separated by separator.



629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
# File 'lib/nl/knd_client/em_knd_client.rb', line 629

def set_zone zone, separator="\n", &block
  zone = zone.clone

  name = zone['name']
  zone.delete 'name'
  if name == nil || name.length == 0
    if block != nil
      block.call false, "No zone name was given."
    end
    return
  end
  if !@@zones.has_key? name
    if block != nil
      block.call false, "Zone #{name} doesn't exist."
    end
    return
  end

  all = ['xmin', 'ymin', 'zmin', 'xmax', 'ymax', 'zmax'].reduce(true) { |has, attr|
    has &= zone.has_key? attr
  }

  if all
    cmd = EMKndCommand.new 'setzone', name, 'all',
      zone['xmin'], zone['ymin'], zone['zmin'],
      zone['xmax'], zone['ymax'], zone['zmax']

    if block != nil
      cmd.callback { |cmd|
        @@zones.has_key?(name) && @@zones[name].merge_zone(zone)
        block.call true, cmd.message
      }
      cmd.errback {|cmd| block.call false, (cmd ? cmd.message : 'timeout')}
    end

    do_command cmd

    ['xmin', 'ymin', 'zmin', 'xmax', 'ymax', 'zmax'].each do |key|
      zone.delete key
    end
  end

  # Send values not covered by xmin/ymin/zmin/xmax/ymax/zmax combo above
  unless zone.empty?
    # TODO: Extract a multi-command method from this
    zone = zone.clone
    zone.delete 'name'

    if zone.length == 0
      if block != nil
        block.call false, "No parameters were specified."
      end
      return
    end

    result = true
    messages = []
    cmds = []
    count = 0
    func = lambda {|msg|
      messages << msg
      count += 1
      if block != nil and count == cmds.length
        block.call result, messages.join(separator)
      end
    }

    zone.each do |k, v|
      if v == true
        v = 1
      elsif v == false
        v = 0
      end
      cmd = EMKndCommand.new 'setzone', name, k, v
      cmd.callback { |cmd|
        @@zones.has_key?(name) && @@zones[name][k] = v
        func.call cmd.message
      }
      cmd.errback { |cmd|
        result = false
        func.call (cmd ? cmd.message : 'timeout')
      }
      cmds << cmd
    end
    cmds.each do |cmd| do_command cmd end
  end
end

#subscribe(&block) ⇒ Object

Subscribes to zone updates. The given block will be called with a success message on success. The return value is the command object that represents the subscribe command (e.g. for adding an errback).



614
615
616
617
618
# File 'lib/nl/knd_client/em_knd_client.rb', line 614

def subscribe &block
  do_command 'sub' do |cmd|
    block.call cmd.message if block != nil
  end
end

#unbindObject



483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
# File 'lib/nl/knd_client/em_knd_client.rb', line 483

def unbind
  begin
    if @tcp_connected
      log "Disconnected from camera server (connection #{@thiscon})."
      @@connect_cb.call false if @@connect_cb

      if @@connected
        now = Time.now
        elapsed = now - @@connection_time
        @@connection_time = now
        call_cbs :online, false, elapsed
      end

      @cbs.clear
    end

    EMKndClient.clear_images

    @@connected = false
    @@fps = 0
    @@instance = nil
    @fpstimer.cancel if @fpstimer
    @zonetimer.cancel if @zonetimer
    @imagetimer.cancel if @imagetimer
    @refreshtimer.cancel if @refreshtimer

    @commands.each do |cmd|
      cmd.err_line "Connection closed"
    end

    @requests.each do |k, v|
      v.each do |req|
        req.call @@images[k]
      end
    end

    if @quit
      EventMachine::stop_event_loop
    else
      EM::Timer.new(1) do
        EM.connect(@@hostname, 14308, EMKndClient)
      end
    end
  rescue => e
    log e
    Kernel.exit
  end
end