module IPCam

Constants

BASIS_SIZE
Restart
Stop
VERSION

Public Class Methods

abort?() click to toggle source
# File lib/ipcam/main.rb, line 401
def abort?
  @state == :ABORT
end
add_client(que) click to toggle source
# File lib/ipcam/main.rb, line 377
def add_client(que)
  @clients << {:que => que}
end
alive?() click to toggle source
# File lib/ipcam/main.rb, line 397
def alive?
  @state == :ALIVE
end
get_camera_info() click to toggle source
# File lib/ipcam/main.rb, line 306
def get_camera_info
  @mutex.synchronize {
    ret = {
      :device => $target,
      :state  => @state
    }

    ret.merge!(:bus => @camera.bus, :name => @camera.name) if @camera

    return ret
  }
end
get_config() click to toggle source
# File lib/ipcam/main.rb, line 324
def get_config
  raise("state violation") if @state != :ALIVE
  return @config
end
get_ident_string() click to toggle source
# File lib/ipcam/main.rb, line 319
def get_ident_string
  raise("state violation") if @state != :ALIVE
  return "#{@camera.name}@#{@camera.bus}"
end
remove_client(que) click to toggle source
# File lib/ipcam/main.rb, line 381
def remove_client(que)
  @clients.reject! {|c| c[:que] == que}
end
save_config() click to toggle source
# File lib/ipcam/main.rb, line 368
def save_config
  $logger.info('main') {"save config to #{$db_file.to_s}"}
  @mutex.synchronize {
    $db_file.binwrite(@db.to_msgpack)
  }

  broadcast(:save_complete)
end
set_control(id, val) click to toggle source
# File lib/ipcam/main.rb, line 352
def set_control(id, val)
  raise("state violation") if @state != :ALIVE

  entry = nil

  @mutex.synchronize {
    entry = @config[:controls].find {|obj| obj[:id] == id} 
    entry[:value] = val if entry
  }

  if entry
    restart_camera()
    broadcast(:update_control, id, val)
  end
end
set_framerate(num, deno) click to toggle source
# File lib/ipcam/main.rb, line 341
def set_framerate(num, deno)
  raise("state violation") if @state != :ALIVE

  @mutex.synchronize {
    @config[:framerate] = [num, deno]
  }

  restart_camera()
  broadcast(:update_framerate, num, deno)
end
set_image_size(width, height) click to toggle source
# File lib/ipcam/main.rb, line 329
def set_image_size(width, height)
  raise("state violation") if @state != :ALIVE

  @mutex.synchronize {
    @config[:image_width]  = width
    @config[:image_height] = height
  }

  restart_camera()
  broadcast(:update_image_size, width, height)
end
start() click to toggle source
# File lib/ipcam/main.rb, line 19
def start
  restore_db()

  @mutex   = Mutex.new
  @camera  = nil
  @state   = :STOP
  @img_que = Thread::Queue.new
  @clients = []

  start_thread()

  WebServer.start(self)
  WebSocket.start(self)

  EM.run
end
start_camera() click to toggle source
# File lib/ipcam/main.rb, line 385
def start_camera
  raise("state violation") if @state != :STOP and @state != :ABORT

  start_thread()
end
stop() click to toggle source
# File lib/ipcam/main.rb, line 36
def stop
  stop_thread()

  WebServer.stop
  EM.stop
end
stop?() click to toggle source
# File lib/ipcam/main.rb, line 405
def stop?
  @state == :STOP
end
stop_camera() click to toggle source
# File lib/ipcam/main.rb, line 391
def stop_camera
  raise("state violation") if @state != :ALIVE

  stop_thread()
end

Private Class Methods

broadcast(name, *args) click to toggle source
# File lib/ipcam/main.rb, line 222
def broadcast(name, *args)
  WebSocket.broadcast(name, *args)
end
camera_thread() click to toggle source
# File lib/ipcam/main.rb, line 239
def camera_thread
  $logger.info("main") {"camera thread start"}

  @camera = Video4Linux2::Camera.new($target)
  if not @camera.support_formats.any? {|x| x.fcc == "MJPG"}
    raise("#{$target} is not support Motion-JPEG")
  end

  snd_thr = Thread.new {sender_thread}

  begin
    @mutex.synchronize {
      @config = load_settings()

      @camera.start
      change_state(:ALIVE)
    }

    loop {
      @img_que << @camera.capture
    }

  rescue Stop
    $logger.info("main") {"accept stop request"}
    change_state(:STOP)

  rescue Restart
    $logger.info("main") {"restart camera"}
    @camera.stop
    retry

  ensure
    @camera.stop if (@camera.busy? rescue false)
  end

rescue => e
  $logger.error("main") {"camera error occured (#{e.message})"}
  change_state(:ABORT, :force)

ensure
  @camera&.close
  @camera = nil

  snd_thr&.raise(Stop)
  snd_thr&.join

  $logger.info("main") {"camera thread stop"}
end
change_state(state, force = false) click to toggle source
# File lib/ipcam/main.rb, line 227
def change_state(state, force = false)
  flag = @mutex.try_lock

  if @state != state or force
    @state = state
    broadcast(:change_state, state)
  end

  @mutex.unlock if flag
end
create_capability_list(cam) click to toggle source
# File lib/ipcam/main.rb, line 89
def create_capability_list(cam)
  ret = cam.frame_capabilities(:MJPEG).inject([]) { |m, n|
    m << pack_capability(n)
  }

  return ret
end
create_control_list(cam) click to toggle source
# File lib/ipcam/main.rb, line 138
def create_control_list(cam)
  ret = cam.controls.inject([]) { |m, n|
    case n
    when Video4Linux2::Camera::IntegerControl
      m << pack_integer_control(n)

    when Video4Linux2::Camera::BooleanControl
      m << pack_boolean_control(n)

    when Video4Linux2::Camera::MenuControl
      m << pack_menu_control(n)

    else
      raise("Unknwon control found #{n.class}")
    end
  }

  return ret
end
create_setting_entry(cam) click to toggle source
# File lib/ipcam/main.rb, line 159
def create_setting_entry(cam)
  cap  = select_capabilities(cam)
  rate = cap.rate.sort.first

  ret  = {
    :image_width  => cap.width,
    :image_height => cap.height,
    :framerate    => [rate.numerator, rate.denominator],
    :capabilities => create_capability_list(cam),
    :controls     => create_control_list(cam)
  }

  return ret
end
load_settings() click to toggle source
# File lib/ipcam/main.rb, line 200
def load_settings
  ret = @db.dig(@camera.bus.to_sym, @camera.name.to_sym)

  if not ret
    ret = create_setting_entry(@camera)
    (@db[@camera.bus] ||= {})[@camera.name] = ret

    $db_file.binwrite(@db.to_msgpack)
  end

  @camera.image_height = ret[:image_height]
  @camera.image_width  = ret[:image_width]
  @camera.framerate    = Rational(*ret[:framerate])

  ret[:controls].each { |ctr|
    @camera.set_control(ctr[:id], ctr[:value]) rescue :ignore
  }

  return ret
end
pack_boolean_control(c) click to toggle source
# File lib/ipcam/main.rb, line 113
def pack_boolean_control(c)
  ret = {
    :type  => :boolean,
    :id    => c.id,
    :name  => c.name,
    :value => c.default
  }

  return ret
end
pack_capability(cap) click to toggle source
# File lib/ipcam/main.rb, line 76
def pack_capability(cap)
  ret = {
    :width  => cap.width,
    :height => cap.height,
    :rate   => cap.rate.inject([]) { |m, n|
      m << [n.numerator, n.denominator]
    }
  }

  return ret
end
pack_integer_control(c) click to toggle source
# File lib/ipcam/main.rb, line 98
def pack_integer_control(c)
  ret = {
    :type  => :integer,
    :id    => c.id,
    :name  => c.name,
    :value => c.default,
    :min   => c.min,
    :max   => c.max,
    :step  => c.step
  }

  return ret
end
pack_menu_control(c) click to toggle source
# File lib/ipcam/main.rb, line 125
def pack_menu_control(c)
  ret = {
    :type  => :menu,
    :id    => c.id,
    :name  => c.name,
    :value => c.default,
    :items => c.items.inject({}) {|m, n| m[n.name] = n.index; m}
  }

  return ret
end
restart_camera() click to toggle source
# File lib/ipcam/main.rb, line 55
def restart_camera
  @cam_thr.raise(Restart)
end
restore_db() click to toggle source
# File lib/ipcam/main.rb, line 175
def restore_db
  begin
    blob = $db_file.binread
    @db  = MessagePack.unpack(blob, :symbolize_keys => true)

    @db.keys { |bus|
      @db[bus].keys { |name|
        @db[bus][name.to_s] = @db[bus].delete(name)
      }

      @db[bus.to_s] = @db.deleye(bus)
    }

  rescue
    begin
      $db_file.delete
    rescue Errno::ENOENT
      # ignore
    end
      
    @db  = {}
  end
end
select_capabilities(cam) click to toggle source
# File lib/ipcam/main.rb, line 60
def select_capabilities(cam)
  ret = cam.frame_capabilities(:MJPEG).instance_eval {
    self.sort! { |a,b|
      da = (BASIS_SIZE - (a.width * a.height)).abs
      db = (BASIS_SIZE - (b.width * b.height)).abs

      da <=> db
    }

    self.first
  }

  return ret
end
sender_thread() click to toggle source
# File lib/ipcam/main.rb, line 289
def sender_thread
  $logger.info("main") {"sender thread start"}

  loop {
    data = @img_que.deq
    @clients.each {|c| c[:que] << data}
    broadcast(:update_image, {:type => "image/jpeg", :data => data})
  }

rescue Stop
  @clients.each {|c| c[:que] << nil}

ensure
  $logger.info("main") {"sender thread stop"}
end
start_thread() click to toggle source
# File lib/ipcam/main.rb, line 43
def start_thread
  @cam_thr = Thread.new {camera_thread}
end
stop_thread() click to toggle source
# File lib/ipcam/main.rb, line 48
def stop_thread
  @cam_thr.raise(Stop)
  @cam_thr.join
  @cam_thr = nil
end