システム概要
UNIQLO や図書館, 倉庫での在庫管理等によく用いられコンビニへの導入検討で最近注目されている UHF 帯 RFID を使用した簡単な入退室および出退勤を記録するシステムを作成.
機能
- 常時, 出入口付近に設置された 2つ以上のアンテナがタグ(人物)の監視を行い, 入退室の記録を行う. この時点での打刻は行わない.
- 入退室記録をもとに
cron
コマンドによりユーザデータ及び出退勤(打刻)情報の記録
構成図
- リーダライタ(Impinj R420), アンテナ
リーダに接続するアンテナは 1(内側), 3(外側) の 2つ. 人が通過した際, 2つのアンテナでタグが検出されないように出力及び設置位置を調整.
1 -> 3 の順でタグを検出した場合, 退室
3 -> 1 の順でタグを検出した場合, 入室
天井や壁に設置してお互いの電波が干渉しないように調整. - PC, データベース
データベースには人事労務 API より取得した従業員データに重複しないタグ ID を紐づけてデータを格納.
履歴テーブルに何処に設置したか識別できるよう読込を行ったリーダ ID と従業員 ID, ステータス(Entry, Leaving), 通過日時を格納. - freee API
設置結果
100% までとは行かないが, ある程度正確に入退室データの記録ができ打刻への追加に成功.
問題点
ある程度動作を行い生じてきた問題を列挙.
- 出勤時の入室, 退勤時の退室が記録されていない最悪の状態
タイムレコーダに記録する際の基準となる情報が取得できていない. そのため信頼できないデータになっている. - 入退室履歴のステータスに同じものが連続する
入退室どちらかのデータに欠損が生じ, 入室および退室が連続してしまった.
改善点など
- 正確に出退勤を記録できていない場合の通知.
- 上記の状態でブラウザからの申請を可能にする.
- あまりあっていはいけないが, 日を跨いでの退室(退勤)を検出した場合を考慮.
-
cron
コマンドを多用せずプログラム中でトークンの更新を行う.
Appendix
systemctl
や Service
などで動作させることを目的にしていなかったため, すごく散らかってますがソースコード.
freee API
タイムレコーダ(打刻)機能との同期については, 上述の改善点に修正を加えて追加する予定(エターナるかもしれません).
/// WARNING: cron
コマンドによりアクセストークンの更新を行っているため, 若干半自動.
entleaving - github
機材や RFID について
使用した機材と注意点, RFID について記載します.
UHF 帯 RFID リーダライタ - Impinj R420
Impinj 社製 UHF 帯 RFID リーダライタです.
電源に AC アダプタもしくは Power over Ethernet(PoE) が使用できます.
Low-Level Reader Protocol(LLRP) を使用して開発を行います.
!! 本記事で使用しているリーダの出力は 1[W] であるため電波申請が必要となります. !!
特定小電力無線局である 250[mW] 以下のリーダライタの場合は不要となります.
Radio Frequency IDentification(RFID)
本記事で使用した RFID は UHF 帯 RFID であり, 通信には電波方式を用いています. この RFID の特徴は長距離での通信が可能であることと複数のRFIDタグを同時に読み込めることです.
日常で使用している RFID として, Suica や PASMO 等が挙げられます.
これらの RFID は NFC の一種であり, 電磁誘導方式での給電とデータ通信を行っているため名称の如く近接距離(最大でも 10[cm] 程)となっております.
Impinj R420 | RFID Tag |
---|---|
システム構成
名称 | 備考 | |
---|---|---|
言語 | C# | .NET5, Version 9.0 |
ライブラリ | LLRP | 10.40.0 |
Newtonsoft.Json | 12.0.3 | |
Npgsql | 5.0.0 | |
Dapper | 2.0.78 | |
データベース | PostgreSQL | 13.1 |
UHF RFID リーダ | Impinj R420 |
データベース(テーブル)構成
create extension if not exists "uuid-ossp";
/** リーダテーブル - リーダに対応する設定は別ファイル
*/
create table if not exists readers(
id uuid not null,
hostname character varying(256) not null,
location_name character varying(128),
remarks character varying(128),
created_at timestamp with time zone default CURRENT_TIMESTAMP not null,
primary key(id)
);
/** 従業員テーブル
*/
create table if not exists employees(
id integer not null,
tag_id character varying(64),
created_at timestamp with time zone default CURRENT_TIMESTAMP not null,
primary key(id)
);
/** (入退室)履歴
*/
create table if not exists histories(
id bigserial not null,
reader_id uuid not null references readers(id),
employee_id integer not null references employees(id),
status smallint not null,
created_at timestamp with time zone default CURRENT_TIMESTAMP not null,
primary key(id)
);