04

16

[Ruby] ネットワーク接続されたプリンタのステータスを取得するプログラム

2014.04.16(22:39)

ネットワーク接続されたプリンタのステータスを取得するRubyプログラム。

ライブラリを使わずSNMPパケットを自分で作って送信し、レスポンスを受信する。
IPアドレスを持ち、SNMPプロトコルをサポートし、MIBを持つネットワークプリンタから、
sysName,sysUpTime,hrDeviceStatus,hrPrinterStatusの4つを取得して表示する。


# encoding: Shift_JIS
require 'socket'

# printer_status.rb
# SNMPクライアントプログラム (Ruby2.0.0)
# ライブラリを使わずSNMPパケットを自分で作って送信し、レスポンスを受信する。
# sysName,sysUpTime,hrDeviceStatus,hrPrinterStatusの4つを取得する
# @see http://www.ietf.org/rfc/rfc1759.txt Printer MIB
# @note 使い方 ruby printer_status.rb プリンタのIPアドレス
# @author @saltheads
# @version 1.0.0

# Hex Dump クラス
class HexDump
  # 1行ダンプ
  # @param [String] data ダンプする対象データ
  # @param [Integer] offset 先頭アドレス
  # @param [Integer] step 1行あたりのデータ数
	def self.dumpline(data,offset,step)
	  if (data.length > 0) then
	    line = sprintf("%08x  ",offset)
	    chars = ""
	    data.each_byte{ |c| 
	      line << sprintf("%02x ",c)
	      chars << ((c >= 0x20 && c <= 0x7e) ? c : '.')
	    }
	    line << "   "*(step - data.size + 1)
	    line << chars
	    puts line
	  end
	end
  # データ全体をダンプ
  # @param [String] fulldata ダンプする対象データ
	def self.dump(fulldata)
	  offset = 0
	  step = 16
	  while data = fulldata[offset,step]
	    dumpline(data,offset,step)
	    offset += step
	  end
	end
end

# Snmp Client クラス
class SnmpClient
  # コンストラクタ
  # @param [String] host IPアドレス("1.2.3.4")
  # @param [Integer] port snmp ポート番号 通常161
  # @return [SnmpClient] オブジェクト
  def initialize(host,port=161)
    @host = host
    @port = port
    @debug = true & false
    printf "host #{host}\n" if @debug
  end
  # パケットダンプ
  # @param [String] title タイトル文字列
  # @param [String] data ダンプする対象データ
  def dump(title,data)
    if @debug
      printf "#{title}\n"
      HexDump.dump(data)
    end
  end
  # UDPでrequestを送信し、受信したデータを返す
  # @param [String] request 送信データ
  # @return [String] 受信したデータ
  def sendReceive(request)
    us = UDPSocket.open()
    us.connect(@host,@port)
    us.send(request, 0)
    a=select([us],nil,nil,3)
    if not a     # Timeout
      us.close
      return nil
    end
    response = us.recv(2000,0)
    us.close
    response
  end
  # スペース区切りの文字列から送信データを作る
  # @param [String] packet スペース区切り文字列
  # @return [String] 変換したバイナリデータ
  def packet2data(packet)
    array = packet.split(' ')
    request = ""
    array.each{ |c| request += c.to_i(16).chr }
    request
  end
  # sysName を取得する
  # @return [String] 受信したsysName
  def sysName
    packet  = "30 27 02 01 00 04 06 70 75 62 6c 69 63 a0 1a 02 "
    packet += "02 03 e8 02 01 00 02 01 00 30 0e 30 0c 06 08 2b "
    packet += "06 01 02 01 01 05 00 05 00 "    

    request = packet2data(packet)
    dump('send',request)    
    response = sendReceive(request)
    return if response == nil
    dump('recv',response)    

    len = response[40].bytes.to_a[0]
    len -= 1
    value = response[41..(41+len)]
    hexdumpdata(value) if @debug
    printf("sysName [#{value}]\n") if @debug
    return value
  end

  # sysUpTime を取得する
  # @return [Integer] 受信したsysUpTime
  def sysUpTime
    packet  = "30 27 02 01 00 04 06 70 75 62 6c 69 63 a0 1a 02 "
    packet += "02 03 e9 02 01 00 02 01 00 30 0e 30 0c 06 08 2b "
    packet += "06 01 02 01 01 03 00 05 00 "
    
    request = packet2data(packet)
    dump('send',request)    
    response = sendReceive(request)
    return if response == nil
    dump('recv',response)    

    time = response[41..43]
    hexdumpdata(time) if @debug
    t = 0
    time.each_byte { |c|
      # printf("%x\n",c)
      t = t*256 + c
    }
    printf("sysUpTime %06x = %d\n",t,t) if @debug
    return t
  end

  # hrDeviceStatusを取得する
  # @return [String] 受信したhrDeviceStatusの意味を表す文字列
  def hrDeviceStatus
    packet  = "30 2A 02 01 00 04 06 70 75 62 6c 69 63 a0 1d 02 "
    packet += "02 03 ea 02 01 00 02 01 00 30 11 30 0f 06 0b 2b "
    packet += "06 01 02 01 19 03 02 01 05 01 05 00 "
    
    request = packet2data(packet)
    dump('send',request)    
    response = sendReceive(request)
    return if response == nil
    dump('recv',response)    

    status = response[44]
    hexdumpdata(status) if @debug
    val = status.bytes.to_a[0]
    case val
    when 1 then s = "unknown(1)"
    when 2 then s = "running(2)"
    when 3 then s = "warning(3)"
    when 4 then s = "testing(4)"
    when 5 then s = "down(5)"
    else s = "unknown(#{val})"
    end
    return s
  end

  # hrPrinterStatusを取得する
  # @return [String] 受信したhrPrinterStatusの意味を表す文字列
  def hrPrinterStatus
    packet  = "30 2A 02 01 00 04 06 70 75 62 6c 69 63 a0 1d 02 "
    packet += "02 03 eb 02 01 00 02 01 00 30 11 30 0f 06 0b 2b "
    packet += "06 01 02 01 19 03 05 01 01 01 05 00 "
    
    request = packet2data(packet)
    dump('send',request)    
    response = sendReceive(request)
    return if response == nil
    dump('recv',response)    

    status = response[44]
    hexdumpdata(status) if @debug
    val = status.bytes.to_a[0]
    case val
    when 1 then s = "other(1)"
    when 3 then s = "idle(3)"
    when 4 then s = "printing(4)"
    when 5 then s = "warmup(5)"
    else s = "unknown(#{val})"
    end
    return s
  end
end

if __FILE__ == $0
  debug = false & true
  host = "10.0.1.99"
  if ARGV.size >= 1
    host = ARGV[0]
  end
  snmp = SnmpClient.new(host)
  name = snmp.sysName
  printf "sysName[#{name}]\n"
  60.times { |i|
    name = snmp.sysName
    time = snmp.sysUpTime
    deviceStatus = snmp.hrDeviceStatus
    printerStatus = snmp.hrPrinterStatus
    printf "sysUpTime[#{time}] deviceStatus[#{deviceStatus}] printerStatus[#{printerStatus}]\n"
    break if debug
    sleep 1
  }
  exit
end
 
 


実行例
プログラムを先に走らせた状態で、プリンタの電源をいれ、紙切れにしたあと、紙をセットし、印刷したようす。
C:>ruby printer_status.rb
sysName[]
sysUpTime[] deviceStatus[] printerStatus[]
sysUpTime[] deviceStatus[] printerStatus[]
sysUpTime[] deviceStatus[] printerStatus[]
sysUpTime[] deviceStatus[running(2)] printerStatus[idle(3)]
sysUpTime[660] deviceStatus[running(2)] printerStatus[idle(3)]
sysUpTime[1396] deviceStatus[running(2)] printerStatus[idle(3)]
sysUpTime[29265] deviceStatus[running(2)] printerStatus[idle(3)]
sysUpTime[29383] deviceStatus[running(2)] printerStatus[other(1)]
sysUpTime[29488] deviceStatus[running(2)] printerStatus[other(1)]
sysUpTime[38746] deviceStatus[down(5)] printerStatus[other(1)]
sysUpTime[38851] deviceStatus[down(5)] printerStatus[other(1)]
sysUpTime[38956] deviceStatus[running(2)] printerStatus[warmup(5)]
sysUpTime[39061] deviceStatus[running(2)] printerStatus[warmup(5)]
sysUpTime[39896] deviceStatus[running(2)] printerStatus[idle(3)]
sysUpTime[40001] deviceStatus[running(2)] printerStatus[idle(3)]
sysUpTime[267976] deviceStatus[running(2)] printerStatus[other(1)]
sysUpTime[268081] deviceStatus[running(2)] printerStatus[other(1)]
sysUpTime[268186] deviceStatus[running(2)] printerStatus[warmup(5)]
sysUpTime[268291] deviceStatus[running(2)] printerStatus[warmup(5)]
sysUpTime[270931] deviceStatus[running(2)] printerStatus[printing(4)]
sysUpTime[271036] deviceStatus[running(2)] printerStatus[printing(4)]
sysUpTime[271141] deviceStatus[running(2)] printerStatus[idle(3)]
sysUpTime[271246] deviceStatus[running(2)] printerStatus[idle(3)]
 
 
私の手持ちのプリンターでは、
印刷できないときに、deviceStatusがdown(5)となり、
印刷中か印刷すべきものが残っているときに、printerStatusがprinting(4)となるらしい。

参考リンク
  1. Printer MIB
    http://www.ietf.org/rfc/rfc1759.txt
プロフィール

島敏博

Shima Toshihiro 島敏博
信州アルプスハイランド在住。HaskellとElixirが好き。組み込みソフトウェアアーキテクト、C++プログラマ、山歩き、美術館巡り、和食食べ歩き、日本赤十字社救急法指導員、インデックス投資、クラシック音楽、SESSAME会員、状態マシン設計、モデル駆動開発、ソフトウェアプロダクトライン、Rubyist、実践ビジネス英語

■ ツイッター
http://twitter.com/saltheads
■ Facebook
http://www.facebook.com/saltheads
■ Qiita
http://qiita.com/saltheads

印刷する場合は、ブラウザの印刷メニューではなく、このページの上から3cmくらいの青いところにある、「印刷」を押してみてください。少しうまく印刷できます。まだ完全ではないのですが、これで勘弁してください。


カテゴリ
最新記事
月別アーカイブ
最新コメント
検索フォーム
リンク
sessame
RSSリンクの表示