シェルスクリプトでIoTをやるにはシリアルポートさえ読めればいいということに気づいたのですが、世の中そんなに甘くはなかった。
シリアルポートでパソコンやIoTゲートウェイに接続される測定器やセンサの中には、データを改行(CR/LF/CRLF)区切りの文字列でくれないものもたくさんあります。
そういったものはASCIIコードとは縁もゆかりもない、1ビット1ビットに丹精に意味が込められた無慈悲なビット列が流れてきます。(シリアル通信の時点で1バイト単位にはなってはいるけれど)
cat
で簡単にシリアルポートからデータが読み出せるといっても、流れているのが改行区切りのテキストではないとすると、シェルスクリプトでもバイナリを扱える必要が出てきます。
1バイトずつ16進数の文字列に変換してしまえばなんとかなるのでは?と思い、まずはその変換をやってみることにしました。
最初、od
コマンドをつかって、以下のようにしようとしました。
cat binary_data | od -vtx1 -An | tr -s ' ' '\n'
ところが、od
が出力をバッファリングしているようで、ファイルに保存済みのバイナリデータを変換するのはうまくいくけれど、1バイトずつリアルタイムに16進数のテキストに変換されてほしいので、これではあまり意味がない。
POSIXコマンドでできる範囲ではどうやら無理そうだったので、諦めてCで短いコードを書きました。(Cは経験があまりないのでこれでいいのかよくわからない・・・)
フォーマットが%02x
になっているのは、1バイトあたり2文字の固定長になるようにしたいからです。
#include <stdio.h>
#include <unistd.h>
int main(void)
{
unsigned char buf[1];
while(read(0, buf, 1) > 0) {
printf("%02x\n", buf[0]);
fflush(NULL);
}
return 0;
}
コンパイルは以下の通り。
c99 byte2hex.c -o byte2hex
1バイト(0x41 = "A")を16進数文字列に変換してみる。
$ printf "%b" "\x41" | ./byte2hex
41
1秒おきに連続でデータを流してみる
$ while :; do printf "%b" "\x41"; sleep 1; done | ./byte2hex
41
41
41
41
41
さらに後ろに別のコマンドをパイプで繋いでもちゃんと流れてくる
while :; do printf "%b" "\x41"; sleep 1; done | ./byte2hex | tee /dev/null
41
41
41
41
41
1バイトが3バイト(2文字+LF)になるのでデータ量は3倍になってしまいますが、これでバイナリを1バイト1行のストリームとして扱えるようになりました。