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

[\[Ruby\] ネットワーク接続されたプリンタのステータスを取得するプログラム](http://saltheads.blog134.fc2.com/blog-entry-146.html) を mruby で動作するよう移植しました。

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

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


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


```rb: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のスクリプトを作成する


```rb: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
```