LoginSignup
6
5

More than 5 years have passed since last update.

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

Posted at

[Ruby] ネットワーク接続されたプリンタのステータスを取得するプログラム を mruby で動作するよう移植しました。

1. mruby をダウンロードする

$ git clone https://github.com/mruby/mruby.git

2. ビルド設定を編集する

build_config.rb
############################
# Start of your build_config

MRuby::Build.new do |conf|
  toolchain :clang # :gcc, :visualcpp

  conf.bins = %w(mrbc)

  # mruby's Core GEMs
  conf.gem 'mrbgems/mruby-bin-mirb'
  conf.gem 'mrbgems/mruby-bin-mruby'
  conf.gem 'mrbgems/mruby-print'
  conf.gem 'mrbgems/mruby-sprintf'

  # user-defined GEMs
  conf.gem :git => 'https://github.com/iij/mruby-io.git'
  conf.gem :git => 'https://github.com/iij/mruby-mtest.git'
  conf.gem :git => 'https://github.com/iij/mruby-socket.git'
  conf.gem :git => 'https://github.com/iij/mruby-pack.git'
end

# End of your build_config
############################

3. ビルドする

$ rake
CC    tools/mrbc/mrbc.c -> build/host/tools/mrbc/mrbc.o
CC    src/array.c -> build/host/src/array.o
... 中略 ...
Build summary:

================================================
      Config Name: host
 Output Directory: build/host
         Binaries: mrbc
    Included Gems:
             mruby-bin-mirb - mirb command
               - Binaries: mirb
             mruby-bin-mruby - mruby command
               - Binaries: mruby
             mruby-print - standard print/puts/p
             mruby-sprintf - standard Kernel#sprintf method
             mruby-io
             mruby-mtest
             mruby-socket
             mruby-pack
================================================

4. mrubyのスクリプトを作成する

printer_status.rb
# printer_status.rb
# SNMPクライアントプログラム (mruby 1.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 = IO.select([us], nil, nil, 3)
    if not a     # Timeout
      us.close
      return nil
    end
    response = us.recv(2000)
    us.close
    response
  end
  # sysName を取得する
  # @return [String] 受信したsysName
  def sysName
    request = [
      0x30, 0x27, 0x02, 0x01, 0x00, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa0, 0x1a, 0x02,
      0x02, 0x03, 0xe8, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x30, 0x0c, 0x06, 0x08, 0x2b,
      0x06, 0x01, 0x02, 0x01, 0x01, 0x05, 0x00, 0x05, 0x00].pack('C*')    
    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
    request = [
      0x30, 0x27, 0x02, 0x01, 0x00, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa0, 0x1a, 0x02,
      0x02, 0x03, 0xe9, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x30, 0x0c, 0x06, 0x08, 0x2b,
      0x06, 0x01, 0x02, 0x01, 0x01, 0x03, 0x00, 0x05, 0x00].pack('C*')
    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
    request = [
      0x30, 0x2A, 0x02, 0x01, 0x00, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa0, 0x1d, 0x02,
      0x02, 0x03, 0xea, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x11, 0x30, 0x0f, 0x06, 0x0b, 0x2b,
      0x06, 0x01, 0x02, 0x01, 0x19, 0x03, 0x02, 0x01, 0x05, 0x01, 0x05, 0x00].pack('C*')
    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
    request = [
      0x30, 0x2A, 0x02, 0x01, 0x00, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa0, 0x1d, 0x02,
      0x02, 0x03, 0xeb, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x11, 0x30, 0x0f, 0x06, 0x0b, 0x2b,
      0x06, 0x01, 0x02, 0x01, 0x19, 0x03, 0x05, 0x01, 0x01, 0x01, 0x05, 0x00].pack('C*')
    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 = "192.168.1.40"
  if ARGV.size >= 1
    host = ARGV[0]
  end
  snmp = SnmpClient.new(host)
  name = snmp.sysName
  puts sprintf "sysName[#{name}]\n"
  60.times { |i|
    name = snmp.sysName
    time = snmp.sysUpTime
    deviceStatus = snmp.hrDeviceStatus
    printerStatus = snmp.hrPrinterStatus
    puts sprintf "sysUpTime[#{time}] deviceStatus[#{deviceStatus}] printerStatus[#{printerStatus}]\n"
    break if debug
    #sleep 1
  }
  #exit
end

CRubyスクリプトからの修正点

4.1 バイト列の生成方法の変更

変更前

mruby-string-utf8 を利用
Integer#chr で意図せぬデータ化けが発生

変更後

mruby-pack を利用
Array#pack で配列をバイト列に変換

4.2 システムコールの削除

sleep と exit をコメントアウト

4.3 select() の呼び出し方法変更

mruby では I/O も外部ライブラリ(mrbgems)に依存するため IO.select() で呼び出し。

5. 上記のスクリプトを実行する

$ cd bin
$ ./mruby printer_status.rb
6
5
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5