NanoPi NEO2とICカードリーダーでタイムレコーダーを作る

NanoPi NEO2とICカードリーダーでタイムレコーダーを作る

この記事はNanoPi NEO2を使うということになってるけどNanoPi NEO2でなければならない要素はゼロで、Linuxが動く他のシングルボードコンピュータでもPCでも殆ど何でもOK。

事業所によくある従業員の出退勤を管理するためのタイムレコーダーと従業員各自用のタイムカード(消耗品)。よく考えると買うのが馬鹿らしいものの一つ。そこでNanoPi NEO2とICカードリーダー、そして従業員が持っているSuicaなどのFelicaカードを使ってタイムレコーダーを作る。ようするに事業所の入り口近くにICカードリーダーを置いといて、従業員が出勤時・退勤時に「ピッ」ってやるのを作る。集計は電子データを適当にゴニョゴニョするか何か作っちゃうか。とにかく好きにできるのが良いのよ。

とりあえずこのページでは使用するのはFelicaカードに限るとする。他のカードを入れると面倒だし。

今回はかなり古いSonyのFelicaカードリーダーS330をNanoPi NEO2とUSBで繋いだ。特にS330でなくてはならないということではない。どこのご家庭でも使われずに放置されてるICカードリーダーが1グロスほどあるかと思うのでテキトーなのを繋ぐ。2021年11月追記: どのカードリーダーでも使えるということではないみたい。また、使用するlibnfcのバージョンによっても利用できたりできなかったり動くようで変なエラーになったり。ハマると結構やっかいかも。
USBで繋いだ後にNanoPi NEO2側でdmesgの最後を見る。

[ 5645.339189] usb 8-1: new full-speed USB device number 2 using ohci-platform

接続したことが認識されたっぽい。

$ lsusb
Bus 008 Device 002: ID 054c:02e1 Sony Corp. FeliCa S330 [PaSoRi]
Bus 008 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 007 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 004 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

USBのデバイスとしてS330カードリーダーが認識されている。システムにlsusbが入っていない場合はこの下でusbutilsをインストールするのでその後で再度確認して欲しい。

libnfcをインストール。 armbianなら以下3つだけでいい筈。

# apt install libnfc-bin libnfc5 libnfc-examples

nfc-pollを使いたいのでlibnfc-examplesも入れる。

FriendlyArmの公式で提供されているNanoPi NEO2用Ubuntu core 16.04を使用しているなら上の3つの他に以下も追加。

# apt install curl usbutils

カードリーダーを接続すると自動的にpn533_usb, pn533, nfcなどのモジュールがロードされることがある。これらは以下の動作テスト以降ずっと邪魔になる。ブラックリストに登録しておくのが無難だが、おそらく既に登録してくれている筈。/etc/modprobe.d/blacklist-libnfc.confが作成されていることを確認する。(ブラックリスト入りさせるモジュールが足りていないことがあるのでlsmodなどで確認して必要に応じて追加。lsmodでpn533_usbが表示されていてblacklist-libnfc.confに入っていないなら足りないということ。) 無ければ/etc/modprobe.d/blacklist-libnfc.confのようなファイルを自身で作成。blacklist nfcとblacklist pn533とblacklist pn533_usbの3行を書き込んで保存。システムを再起動するのがイヤで以下を実行するならmodprobe -r pn533_usbとmodprobe -r nfcとmodprobe -r pn533を実行。(lsmodでpn533_usbが表示されるならpn533_usbを最初に外す)

ICカードリーダー動作テスト

カードリーダーが使えるか確認
# nfc-scan-device -v
nfc-scan-device uses libnfc 1.7.1
1 NFC device(s) found:
- Sony / ?:
    pn53x_usb:008:002
chip: PN533 v1.48
initator mode modulations: ISO/IEC 14443A (106 kbps), FeliCa (424 kbps, 212 kbps), ISO/IEC 14443-4B (106 kbps), Innovision Jewel (106 kbps), D.E.P. (424 kbps, 212 kbps, 106 kbps)
target mode modulations: ISO/IEC 14443A (106 kbps), FeliCa (424 kbps, 212 kbps), D.E.P. (424 kbps, 212 kbps, 106 kbps)

ICカードリーダーにカードを置かないで実行。

# nfc-list
nfc-list uses libnfc 1.7.1
NFC device: Sony / ? opened

情報らしい情報を返してこなかった。(正常)

SuicaでもPasmoでも何でもいいのでFelicaのカードをカードリーダーに置いた状態で実行。

$ nfc-list -v
nfc-list uses libnfc 1.7.1
NFC device: Sony / ? opened
0 ISO14443A passive target(s) found.

1 Felica (212 kbps) passive target(s) found:
FeliCa (212 kbps) target:
        ID (NFCID2): 00  00  00  00  00  00  00  00  
    Parameter (PAD): 00  00  00  00  00  00  00  00  
   System Code (SC): 00  00 

0 Felica (424 kbps) passive target(s) found.

0 ISO14443B passive target(s) found.

0 ISO14443B' passive target(s) found.

0 ISO14443B-2 ST SRx passive target(s) found.

0 ISO14443B-2 ASK CTx passive target(s) found.

0 Jewel passive target(s) found.

実際には00の部分は00〜ffが入っているがあえて全て00に置換している。
1 Felica (xxx kbps) passive target(s) foundと表示されると共にID, Parameter, System Codeが返ってきた。なお、カードの種類によって何が返ってくるかは違うみたい。
上3つの確認で Sony / ?: となっているが、?の部分は本来はデバイス名が入るところの筈。今回はどうでもいいので無視で。

nfc-pollも実行してみる。もしlibnfc.driver.pn53x_usb関係のエラーが出たとしても動いてれば(NFCID2が読めていたら)取り敢えず無視。

NanoPi NEO単独で記録の場合

simple_felica_recorder.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/bin/sh
LOGFILE=/var/log/card.log

echo none > /sys/class/leds/nanopi:blue:status/trigger

while :
do
    nid=$(nfc-poll | grep NFCID2 | cut -d: -f 2 | sed 's/ //g')
    if ! [ x"$nid" = x ];then
        echo `date '+%Y/%m/%d %H:%M:%S'` $nid >>$LOGFILE
        echo heartbeat > /sys/class/leds/nanopi:blue:status/trigger
        sleep 1
        echo none > /sys/class/leds/nanopi:blue:status/trigger
        sleep 1
    fi
done

スクリプトはループで内容を実行。カードリーダーがFelicaカードを読んだ場合はその時刻とFelicaのNFCID2をログに書き出す。連続してカード情報を読み込むのを防ぐため2秒停止する。
読み取るのはNFCID2だけでPADとSCはこのページでは無視することにする。
nfc-listを使用するのもアリだが、読み取りが遅くなるのでカードをリーダーにかざして2秒ほど待つことになる。使い勝手が良くないしせっかくの高速なFelicaが生かされない。nfc-pollはカードを待ち受けてかざされたらすぐに反応するのでこちらを採用。

4, 11, 13行目、armbianの最新カーネルのNanoPi NEO2用はあちこち未完成なので/sys/class/leds/nanopi:blue:statusが存在しない(2017年7月30日現在)。/sys/class下に緑・青のLEDを制御できるディレクトリが存在しない状態なのでコメント化する。

$ chmod +x simple_felica_recorder.sh
$ ./simple_felica_recorder.sh > /dev/null

スクリプトに実行権限を付けて実行。カードをかざしてみる。

ログには「日付 時刻 NFCID2」が1行ごとに記録されるのでCSVとして処理すればタイムレコーダー代わりにできる筈。「出勤」と「退勤」は無いけどシフト制勤務でもなければ要らないよね。
なお、上のように記録するだけだと不心得者がデタラメにカードをかざすと記録がノイズだらけになる。(不完全なカードIDが記録されるということではなく不要な記録が入るということ。)

NanoPi NEOからカードIDを別サーバに送信して記録する場合

問題はNanoPi NEO2のストレージが信頼性の欠片も無いmicroSDということ。もしもフラッシュメモリがぶっ壊れたら記録を取り出すことは絶望的で、タイムレコーダーとしては絶対にあってはならない。
そこで、カードの読み取りはNanoPi NEO2が行い、JSONで別サーバーに情報を送りそちらで記録することにする。

Felicaカード情報の送信側
json_felica_recorder.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/bin/sh
HOSTPATH=example.com/hoge/json_recv.php
LOGFILE=/var/log/card.log

echo none > /sys/class/leds/nanopi:blue:status/trigger

while :
do
    nid=$(nfc-poll | grep NFCID2 | cut -d: -f 2 | sed 's/ //g')

    if ! [ x"$nid" = x ];then
        echo `date '+%Y/%m/%d %H:%M:%S'` $nid >>$LOGFILE
        curl http://$HOSTPATH -X POST -H "Content-Type: application/json" -d '{"nid": "'$nid'"}'
        echo heartbeat > /sys/class/leds/nanopi:blue:status/trigger
        sleep 1
        echo none > /sys/class/leds/nanopi:blue:status/trigger
        sleep 1
    fi
done

2行目と3行目を必要に応じて変更。
5, 14, 16行目はNanoPi NEO2のLEDの制御。カードを認識したら青LEDをピカピカさせる。上のスクリプトでも書いたが最新カーネルのarmbianとNanoPi NEO2の組み合わせではLEDの制御が効かないのでコメント化する。

ウェブサーバー側(PHPが入っていること)
json_recv.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
$json_string = file_get_contents('php://input');
$data = json_decode($json_string);
$nid = $data->nid;

$file = '/var/log/card.log';
$current = file_get_contents($file);
$current .= date('Y/m/d H:i:s ') . $nid . "\n";
file_put_contents($file, $current);
?>

このjson_recv.phpはhttp://example.com/hoge/json_recv.phpでアクセスできるところに置く。
NanoPi NEOからJSON形式でPOSTされたカードIDを日時と共に/var/log/card.logに追記する。ただそれだけ。

実際にはログに書くのではなくデータベースに記録するとともにカードIDが登録済みであるか確認しカードの所有者名を返すとか前回の記録時間と比較して異常がないか(当日の出勤記録が無いとか前日の退勤記録が無いとか)を返すようにするのが良さげ。NanoPi NEO(json_felica_recorder.sh)側も返された値を読み上げるとか何かの合図の音を出すなどしてやらないと利用者(従業員)が不安になると思われる。逆にそのあたりをしっかりやれば出勤時専用・退勤時専用のリーダーを2つ用意する必要はないといえる。

関連記事: