Brotherの複合機の通信プロトコル解析(スキャナ部分)(3) - スキャナへのPCの登録
以下の続き。
本題のスキャンデータの取り込み部分をまとめていたらめんどくさくなってきたので、やっぱり簡単なほうから先に書くことにする。スキャナ側の操作で取り込み先として指定するPC一覧に表示されるPCの登録方法についてまとめる。
以下、PC名をhoge、IPアドレスを192.168.0.1、スキャナのIPアドレスを192.168.0.2とする。スキャナへのPCの登録は、概要にも書いたように、PCからスキャナにSNMPのSet-Requestを送ることで行う。
- OID: 1.3.6.1.4.1.2435.2.3.9.2.11.1.1.0
- community: internal
に対して、以下のようなSet-Requestを送る。
TYPE=BR;BUTTON=SCAN;USER="hoge";FUNC=IMAGE;HOST=192.168.0.1:54925;APPNUM=1;DURATION=360;
TYPE=BR;BUTTON=SCAN;USER="hoge";FUNC=OCR;HOST=192.168.0.1:54925;APPNUM=3;DURATION=360;
TYPE=BR;BUTTON=SCAN;USER="hoge";FUNC=EMAIL;HOST=192.168.0.1:54925;APPNUM=2;DURATION=360;
TYPE=BR;BUTTON=SCAN;USER="hoge";FUNC=FILE;HOST=192.168.0.1:54925;APPNUM=5;DURATION=360;
末尾に改行は含まない。4つのリクエストを同じOID宛に同時に送る。FUNC=に対応する値IMAGE、OCR、EMAIL、FILEが名前のとおりそれぞれのメニュー項目に対応する。ここで、1つのデータしか送らなかった場合などは、指定されなかったデータは登録を抹消される。
- TYPE: BR固定。ブラザー?
- BUTTON: SCAN固定。
- USER: 表示されるPC名。ASCII文字以外は"?"となるようだ。最長16文字。PC名に設定された'"'やその他記号のエスケープはしていない。
- FUNC: メニュー項目。
- HOST: スキャナからのスキャンのリクエストを受け付けるPCのIPアドレスとポート。ここにUDPのパケットが送られる。
- APPNUM: スキャンのリクエスト時に一緒に送られる。Brotherのアプリケーションでは、IMAGE: 1、OCR: 3、EMAIL: 2、FILE: 5となっているが、ほかの値でも問題ない。
- DURATION: スキャナに登録される有効時間を秒単位で指定する。Brotherのアプリケーションでは、360秒を指定している。有効時間内に再度登録された場合は、(長くても短くても)新しい値で上書きされる。最長でどの程度まで有効なのかは未調査。
- BRID: パスワードを設定した場合は、ここに16進8桁の値が設定される。パスワードを設定しない場合はこの項目はない。
以上の値を設定し、上の例のように値の後ろにセミコロンをつけてつなげる。USERに設定されたPC名とBRIDの組が各PCを区別するキーとなる。再度Set-Requestを送ったときに、これらが同じ場合上書きされ、どちらか片方でも異なる場合(IPアドレスが同じでも)別PCとして新規登録される。
スキャンキー用のパスワードを設定した場合は、BRIDという項目が増えて16進8桁の値が設定される。パスワードはスキャナ側でチェックするのみで、PC側でのチェックは行わない。前述のとおり、USERとBRIDの組でPCを識別しているため、パスワードの設定を変えると同じ名前のPCが複数登録されるのに、パスワードが異なるので解析中どれがどれかわからなくなって困った。BRIDは、一方向関数でなく、ビットの入れ替えとXORで復元できる簡易的なものだった。あえて解説する必要はないと思うので、割愛する。
というわけで通信手順がわかったので、スキャナにPCを登録するRubyスクリプトを書いてみた。SNMPプロトコルによるリクエストの送信には、
http://members.at.infoseek.co.jp/m6809/index-j.html
のs2nmpを使わせていただいた。
require 'socket' require 's2nmp' OID = '1.3.6.1.4.1.2435.2.3.9.2.11.1.1.0' PC_ADDR = '192.168.0.1' PC_PORT = 54925 PC_NAME = 'hoge' SCANNER_ADDR = '192.168.0.2' functions = [ {:FUNC => 'IMAGE', :APPNUM => 1}, {:FUNC => 'OCR' , :APPNUM => 3}, {:FUNC => 'EMAIL', :APPNUM => 2}, {:FUNC => 'FILE' , :APPNUM => 5}, ] request = functions.map do |func| [OID, 4, %Q{TYPE=BR;BUTTON=SCAN;USER="#{PC_NAME}";FUNC=#{func[:FUNC]};HOST=#{PC_ADDR}:#{PC_PORT};APPNUM=#{func[:APPNUM]};DURATION=360;}] end snmp = SNMP.new(SCANNER_ADDR) snmp.set('internal', request)
スクリプトを実行し、その後スキャナを操作してみると、"hoge"というPCが選択肢に現れていることを確認できた。ここで、"スキャンの実行"までしてしまうと、スキャンを行う部分はまだ用意していないので、タイムアウトまで長い間スキャナの操作できなくなるので注意。
PCの登録部分なら簡単なのですぐに書き終わるかと思ったら意外と長くなった。今回はこれで終わり。
Brotherの複合機の通信プロトコル解析(スキャナ部分)(2) - 概要
先日(id:ke-k:20100426:brother_scanprotocol)の続き。BrotherのMyMio(おそらくJUSTIOも同じ)で、ネットワーク経由でスキャンするときにどういうやり取りしてるの?って話です。ようやく本題。
Brotherのネットワークスキャナでは、スキャナ、PCのそれぞれ以下のポートで待ち受けている。
- スキャナ側
- PC側
スキャンを行う大まかな流れは以下のようになっている。
- スキャナ起動時
- スキャナへのデータ取り込み先PCの登録
- スキャナ側の操作で、保存先PCを選んで「スタートボタン」を押したとき
- スキャンの実行
- PCからスキャナの54921番ポートにTCP接続し、スキャンを実行する。
PCからスキャンしたいときは、1-3を省略して、4のスキャンの実行をすればOK。PCのControlCenterからスキャンを実行するのと、スキャナ側の操作でPCにデータを送信するのに、本質的な違いはなかった。スキャナ側のパネルで操作してPCに保存を選んだときは、スキャナからPCに「スキャンしてくれー」、と1つパケットを投げるだけで、あとはPCからのスキャンの実行を待つ。ここで、PCが何もしないと、スキャナ側ではタイムアウトまで(1分くらい?)操作できない。キャンセルも不可。
あと、PCのシャットダウン時に行うPCの登録解除みたいなのがあるんじゃなかろうか。もしくは有効期限切れになって消えるまでそのままかもしれない。
さて、大まかな流れを説明したところで、もう飽きてきたので、実際やり取りされるデータの詳細は次回…は、あるんだろうか。
Brotherの複合機の通信プロトコル解析(スキャナ部分)
Brotherの複合機を買った。フラットベッド+ADFのスキャナ、インクジェットプリンタ、コピー機が合わさったMyMio DCP-595CNというやつ。Amazonで1万2千円とちょっと。あと数千円出せばFAX付きのが買えたが、我が家には電話回線がないので、これで十分。
最近、紙類(だけではないが)を棄てられないのが物が多くなる一因だと気づいたので、スキャナで取り込んで電子データ化してどんどん捨てていこうという目論見。うーん、1ヶ月後には挫折していそうだ。
とにかく、そんなわけでスキャナがメイン、あとの機能はおまけで十分。画質はそこまでこだわらないので安くて手軽にスキャンして整理できるのがいい、というわけでこれに決定。ADFつき、PCからのネットワークスキャンのほかに、スキャナ側の操作だけでPCを選んでネットワーク経由でスキャンできたり、USBメモリをスキャナに挿して直接ファイル保存できるなど、安いのになかなか便利そうな機能が豊富なところがいい。
なんだか宣伝みたいになっているな。宣伝ついでに商品へのリンクを張っておこう。
で、先週に届いたので、使ってみた。
・・・うーん、便利ではあるんだけど、かゆいところに手が届かない感じ。あと、これができれば・・・と思うところがいくつか。現在の仕様の中で実現できるはずなのに、敢えて機能を省いているような気がしなくもない。ターゲットがライトユーザなんだろうか。残念ながら、これで書類を全部スキャンしてやるぜ!という気分にはなれなさそうだ。(以上はスキャナの感想。その他の機能はまったく触っていないので。)
前置きが長くなったが、動作仕様の細かなところに思うところがあったのと、ネットワークでスキャンする仕組みが気になったので、週末に解析してみた。通信プロトコル解析、なんて大仰なタイトルをつけたが、パケットを覗き見てちょこちょこっと再現するスクリプトを書いただけ。もっとちゃんとした解析はほかの人がやっている気がする。いやむしろ公式サイトに仕様が公開されてたりして。。。(確認…)なかった。需要があるのかすらわからないが、簡単なサンプルスクリプトの動作まで確認できたので、とりあえずまとめてみる。
つづく。
2年ぶり
前回更新から2年以上経ってた。
いろいろネタはあったような気がするけど、なんだったかな。
rule がうまく機能しない
Rakeで、 rule の依存先が複数の場合、うまく動かないことがあるようです。
以下のような Rakefile の場合、
rule '.foobar' => ['.foo', '.bar'] do |t| sh "cat #{t.prerequisites} > #{t.name}" end rule '.foo' do |t| sh "echo #{t.name} > #{t.name}" end rule '.bar' do |t| sh "echo #{t.name} > #{t.name}" end
この場合、"hoge.foobar" の依存先は ["hoge.foo", "hoge.bar"] になると思うのですが、
% rake hoge.foobar (in /tmp) echo hoge.foo > hoge.foo echo hoge.foo > hoge.foo cat hoge.foo > hoge.foobar
rule の1つ目しか実行されていないようです(しかもなぜか2回)。
ただし例外として、2つ目以降のルールがタスクとして登録されている場合には問題ないようです。
% rake hoge.bar hoge.foobar (in /tmp) echo HOGE.BAR > hoge.bar echo hoge.foo > hoge.foo cat hoge.foo hoge.bar > hoge.foobar
rake.rb のソースを見ると、 attempt_rule 内で
def attempt_rule(task_name, extensions, block, level) sources = make_sources(task_name, extensions) prereqs = sources.collect { |source| if File.exist?(source) || Rake::Task.task_defined?(source) source elsif parent = enhance_with_matching_rule(sources.first, level+1) parent.name else return nil end } task = FileTask.define_task({task_name => prereqs}, &block) task.sources = prereqs task end
enhance_with_matching_rule を呼び出すときに sources.first を渡してますが、ここは source じゃないかと思います。
elsif parent = enhance_with_matching_rule(source, level+1)
とすることで、今回のケースは解決しました。
一応svnのtrunkとのdiffです。
Index: rake.rb =================================================================== --- rake.rb (revision 639) +++ rake.rb (working copy) @@ -1756,7 +1756,7 @@ prereqs = sources.collect { |source| if File.exist?(source) || Rake::Task.task_defined?(source) source - elsif parent = enhance_with_matching_rule(sources.first, level+1) + elsif parent = enhance_with_matching_rule(source, level+1) parent.name else return nil
RakeFileUtils :noop, :verbose の デフォルトオプションがおかしい
id:ke-k:20080211:rakefileutils でもちょろっと書きましたが、 RakeFileUtils がうまく動いていないようです。
nowrite(true) verbose(true) sh 'mkdir hoge' mkdir 'fuga'
とすると、sh には :noop, :verbose オプションが渡されているのですが、 mkdir には渡されていません。
前回も見た
module RakeFileUtils # ... FileUtils::OPT_TABLE.each do |name, opts| default_options = [] if opts.include?('verbose') default_options << ':verbose => RakeFileUtils.verbose_flag' end if opts.include?('noop') default_options << ':noop => RakeFileUtils.nowrite_flag' end next if default_options.empty? module_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{name}( *args, &block ) super( *rake_merge_option(args, #{default_options.join(', ')} ), &block) end EOS end
の部分を見直してみると、 opts.include?('verbose') としてますが、ここにはSymbolが入っているので、opts.include?(:verbose) が正しいですね。 OPT_TALBE['sh'] の代入の部分もおかしいです。
というわけで、svnのtrunkとのdiffです。
Index: rake.rb =================================================================== --- rake.rb (revision 639) +++ rake.rb (working copy) @@ -871,8 +871,8 @@ module FileUtils RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) - OPT_TABLE['sh'] = %w(noop verbose) - OPT_TABLE['ruby'] = %w(noop verbose) + OPT_TABLE['sh'] = [:noop, :verbose] + OPT_TABLE['ruby'] = [:noop, :verbose] # Run the system command +cmd+. If multiple arguments are given the command # is not run with the shell (same semantics as Kernel::exec and @@ -970,10 +970,10 @@ FileUtils::OPT_TABLE.each do |name, opts| default_options = [] - if opts.include?('verbose') + if opts.include?(:verbose) default_options << ':verbose => RakeFileUtils.verbose_flag' end - if opts.include?('noop') + if opts.include?(:noop) default_options << ':noop => RakeFileUtils.nowrite_flag' end
これでOKのはずです。