放射線量可視化(1):線量計DoseRae2 PRM-1200のシリアルポートを利用しよう

注: この作業によって作成した可視化グラフはhttp://FromTo.Cc/rad/で、結果の定期ツイートは@xckbradで公開されています。

空間線量の可視化サイトを作りたい

さて、先日購入した線量計のDoseRae2 PRM-1200ですが、 ここしばらく持ち歩いていろいろチェックしてみていましたが、 そろそろ別のこともやりたいということで…。

この機種は、USB経由でデータをエクスポート可能であるということなので、 買った当初はそれを読みだすプログラムを作れば、 放射線に関する値の可視化ができるね、と思ったのですが、 なんとエクスポートの単位が0.1μSv/hと聞いてがっかり (本体の液晶画面は0.01μSv/h単位なのに)。 それじゃ、0.07〜0.08μSv/h程度のうちのあたりでは、 ずっと0.1μSv/hに張り付いちゃうじゃん…ということになりますよね。

どうもプロトコルでは32ビット整数で線量値を表現しているようなので、 仮に0.01μSv/h単位で渡したとしても、 unsignedでは約43Sv(余裕で致死量)まで測れるはずなんですが…。 わけがわからないよ。

まあ、累積の積算値も0.1μSv/h単位ながらもエクスポートが可能ということで、 ではこの値がインクリメントされた時間などを分析すれば、0.1μSv/h以下の値も推計できるかなぁ、などということを考えていたわけです。 ググッてみると、プロトコルを解析したページもあるしね。

そんなこんなで、いろいろ考えているうちに、 実は10月にDoseRae2の新しいファームウェアが公開されていたことが判明! しかも変更内容が「PC側へのデータ出力を0.01μSv/hに変更されました」のみとのこと。 これでそれなりにマトモな可視化ができますね! 素晴らしい。早速アップデートを実行してみました。

ちなみに、アップデートはDoseRae2に懐かしの8cm CDで付属しているWindows用のユーティリティソフトで行うのですが、 Macbook Airで使うために、まずWindowsマシンで8cm CDをUSBメモリにコピーして、 それをAirで読み取って、Parallelsによる仮想化Windows XPにインストールしました (Macのスロットインタイプのドライブに8cm CDを突っ込む勇気はない…)。 で、ここにDoseRae2を接続し、ダウンロードしたファームウェアをインストールしたわけです。

USBシリアルポートからのデータ取得

ところで、これを何らかの手段で可視化するには、まずはシリアルポートに出力されるデータを、解析するソフトが必要となります。 DoseRae2ファームウェア等のダウンロードページですが、 実はここに「LinuxOS向けユーティリティ」なんてものが置いてあります (Fedora 15/CentOS 5.5用)。

ということで、FreeBSDでrpm2cpioを使ってこのパッケージを展開して、 使えるかどうか見てみたのですが…、 中にRae.pmなんてPerlのクラスが入っていることが判明。 これを使えばいいのかな、などと思ったのですが、一切undocumentedっぽくて、 なおかつちょっと試したけどFreeBSDではすぐには動かなかった…。 え、FedoraやCentOS入れればいいって? なんで趣味でやってるのに好きでもないOSを自腹で買ったPCに入れなきゃいけないのさw そもそもドキュメントないし。

ということで、プロトコル解析ページを参考に、 現在の線量を取ってくるだけという必要最小限の機能を作るのであれば、 Rae.pmを動かす手間よりそっちの方が簡単ではという結論に。

ちなみにDoseRae2はFreeBSDからは、

# usbconfig
...(略)...
ugen3.4: <CP2102 USB to UART Bridge Controller Silicon Labs> at usbus3, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON
# ls -l /dev/cuaU*
crw-rw----  1 uucp  dialer    0, 109 11月 27 23:08 /dev/cuaU0
crw-rw----  1 uucp  dialer    0, 111 11月 27 23:08 /dev/cuaU0.init
crw-rw----  1 uucp  dialer    0, 113 11月 27 23:08 /dev/cuaU0.lock

のようにUSBシリアルポートとして見え、/dev/cuaU0としてアクセス可能です (もちろん最後の番号は、環境によって変わる可能性があります)。

で、PerlのDevice::SerialPortを使って、これをアクセスしてみましょう。 FreeBSDの場合はportsのcomms/p5-Device-SerialPort/にあります。 まずはポートのオープンとシリアルポートのパラメータの初期化。 通信速度は38400bpsです。

#!/usr/bin/perl
use strict;
use Device::SerialPort;
my $device = '/dev/cuaU0';
my $port = open_device($device);
# 以下処理続く
sub open_device($) {
    my $device = shift;
    my $port = new Device::SerialPort($device) or die "cannot open $device";
    $port->user_msg(1);
    $port->error_msg(1);
    $port->baudrate(38400);
    $port->databits(8);
    $port->parity("none");
    $port->stopbits(1);
    $port->handshake("none");
    $port->read_const_time(100);
    $port->read_char_time(5);
    return $port;
}

プロトコル解析ページによれば、 デフォルトでは5秒おきに35バイトのバイナリデータがシリアルポートに出力され、 現在の線量はその27バイト目(0起点)から4バイトの場所に、 0.1μSv/h単位で、ビッグエンディアンのバイナリとして入っているとのこと。 ただし、ファームウェア1.12適用後は、ここが0.01μSv/h単位となるようです。

このデータを読みだして、ファイルに書きこんでみましょう。 上のリストの「# 以下処理続く」の場所にさらに続けます。 読みだした35バイトのバイナリデータを16進数でunpack()して、 27バイト×2文字=54文字目から、4バイト×2文字=8文字分の部分文字列を切り出し、 それをhex()で値に戻して0.01をかけて線量値(μSv/h単位)を取り出しています。

my $timeout = 100;
my $datafile = '/tmp/raedata.txt';
while (my $data = read_data($port, 35)) {
    my $packed = unpack("H*", $data);
    my $usvhex = substr($packed, 54, 8);
    my $usv = hex($usvhex) * 0.01;
    open my $dataf, ">:utf8", $datafile or die "cannot open $datafile";
    print $dataf "RAE${usv}\n";
    close $dataf;
}
sub read_data($$) {
    my ($port, $length) = @_;
    my $l = 0;
    my $ret = '';
    my $timectl = 0;
    while ($l < $length) {
        my ($datalen, $data) = $port->read(1);
        if ($datalen > 0) {
            $ret .= $data;
            $l += $datalen;
            $timectl = 0;
        }
        else {
            $timectl++;
            if ($timectl >= $timeout) {
                die "Device $device timeout";
            }
        }
    }
    return $ret;
}

このプログラム(getraeという名前にしました)を常時バックグラウンドで動かしておくことで、 /tmp/raedata.txtファイルに、 常に次のような形式で現在の線量が書きこまれている状態にすることができるわけです (「RAE」という文字列が入るようにしたのは本質的な意味はなく、なんとなく念のため)。 これは0.08μSv/hを示します。

RAE0.08

無限ループプログラムの突然死対策

このプログラムは無限ループのプログラムなので、 何らかの理由で突然死すると困りますね。

このあたりは定番のテクニックで、このプログラムを/rae/sbin/getraeというファイルにして、実行ビットを立て、 さらに次のようなスクリプトを作ります。 で、あとはこのスクリプトをcronで毎分自動起動にしておけば、 再起動時にも、またgetraeの異常終了時にも、 いつの間にか自動的にgetraeが起動していることでしょう。

#!/usr/bin/perl
use strict;
open my $psf, '/bin/pgrep -fl "perl.*getrae" |' or die "cannot open pgrep";
my $proc = '';
while (<$psf>) {
    chomp;
    if (not /pgrep/) {
        $proc = $_;
    }
}
close $psf;
if ($proc !~ /^\d/) {
    system("/rae/sbin/getrae &");
}

あとは、こうして出てきたデータを可視化するわけですが…。 それはまた続く