Zenfone 2 Laser 替えの裏蓋が届いた

Zenfone 2 Laser背面ハウジング・裏蓋1
Zenfone 2 Laserのバッテリーがご懐妊で書いたがZenFone2 Laserの背面カバーというか裏蓋というか裏側のハウジングケースというか名称に悩むがそれがひび割れて砕けるようになった。そこで7月21日の夜に注文して8月5日に届いたので約2週間。おおよそ予定通り。

Zenfone 2 Laser背面ハウジング・裏蓋2
発泡スチロールの入れ物にビニールに包んだプラスチックの薄い板が入っているようなものなのでめっちゃ軽い。

Zenfone 2 Laser背面ハウジング・裏蓋3
これまで使っていたのが右側の白。新しく届いたのが左の黒(マット)。写真ではわからないだろうけど。黒は黒でもかなり安っぽい。
だから黒だけが極端に安いんだろうけど。

Zenfone 2 Laser背面ハウジング・裏蓋4
裏蓋の内側。中国(AliExpress)で安く売られているものなので「粗悪なコピー品」かと思って心配していたが、少なくとも「粗悪」ではないし断言はできないけどコピー品ではなく純正品っぽい。
左の白カバーの左の辺がガタガタしているのは砕けたのが見える部分。ひび割れもたくさんあるが、これは写真には殆ど写らない程度。最初は何でヒビ割れたり砕けたりしたのか解らなかったが、どうやらバッテリーが膨らんで引っ張られまくったからみたい。妊娠線みたいなの?

粗悪なコピー品を心配して一番安い「黒」(US$5.56 送料無料)を選択したが、まともな品物が届くと解ってたら別の色を選択したのになぁ。
肝心の替えのバッテリーがまだ届いていないのでまだ本体に装着はしていない。(ZenFone2 Laserの裏蓋は一度嵌めると次に開けるのが一苦労、特に新品の蓋はなおさら。)

関連記事:

NanoPi NEO2とICカードリーダーでタイムレコーダーを作る(実用化編)

NanoPi NEO2とICカードリーダーでタイムレコーダーを作る(実用化編)

前回はNanoPi NEO2用タイムレコーダーの超雛形を作ったが、今回はもう少しだけ実用的な雛形にする。

この記事でやること

  • カードの記録をデータベースに格納する
  • ユーザーをデータベースで管理する
  • カード情報をデータベースで管理する
  • カードを読むとユーザー名を読み上げる(音声合成)
  • 出勤と退勤を読み上げる
  • 本日退勤済みの後に再出勤しようとしたら警告する(弾かない)
  • 出勤日時が前日の場合は警告する(弾かない)
  • 前回のカード読み込みから30秒以内に同じカード読み込みで前回と今回を取り消し

この記事でやらないこと

  • 管理ツール作成
  • Felica以外のカードへの対応

当然巨大化するのでブログの1ページでやるようなことじゃない。

MySQLのデータベースとユーザーの作成

$ mysql -u root -pパスワード mysql

mysql> create database timerecorder;
mysql> GRANT ALL PRIVILEGES ON timerecorder.* TO timemgr@localhost identified by 'パスワード';
mysql> FLUSH PRIVILEGES;
mysql> quit;

1行目: MySQLの管理者アカウントでmysqlデータベースに接続
3行目: timerecorderデータベース作成
4行目: localhostでのみ活動可能なtimemgrというユーザーにtimerecorderデータベースの全テーブル対して全権を与える(兼ユーザー作成)
5行目: 2行目で登録した権限を反映
6行目: mysqlデータベースを抜ける

table.sql
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
CREATE TABLE timelog (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`nid` varchar(16) NOT NULL,
`dtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`finout` bit(1) NOT NULL DEFAULT 0,
`flag` bit(1) NOT NULL DEFAULT 1,
PRIMARY KEY (id)
) ENGINE=InnoDB;

CREATE TABLE card (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`nid` varchar(16) NOT NULL,
`uid` int(10) NOT NULL,
`stdate` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`endate` datetime NOT NULL DEFAULT '2199-12-31 23:59:59',
`flag` bit(1) NOT NULL DEFAULT 1,
PRIMARY KEY (id)
) ENGINE=InnoDB;

CREATE TABLE user (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`uname` varchar(32) NOT NULL,
`uruby` varchar(32) NOT NULL,
`stdate` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`endate` datetime NOT NULL DEFAULT '2199-12-31 23:59:59',
`flag` bit(1) NOT NULL DEFAULT 1,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE junk (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`nid` varchar(16) NOT NULL,
`dtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB;

timelogテーブルは利用者がカードリーダーにカードをかざしたときに記録するためのテーブル。junkテーブルも同様だが、timelogテーブルは登録されたカードがかざされた場合に記録され、未登録のカードがかざされた場合はjunkテーブルに入る。
4つのテーブル共にidカラムはオートインクリメントなので基本触らない。
timelog.nid は登録済みのカードのNFCID2が記録される。
timelog.dtime は登録済みのカードがかざされた時間が記録される。
timelog.finout は出勤(1)or退勤(0)が記録される。
timelog.flag は記録として有効(1)か無効(0)かのフラグ。
card.nid はカードのNFCID2が記録される。
card.uid はカード所有者のID(user.id)が記録される。
card.stdate はカードの有効期間の開始日時が記録される。管理用(このページでは未使用)
card.endate はカードの有効期間の終了日時が記録される。管理用(このページでは未使用)
card.flag はカードが有効(1)か無効(0)かのフラグ。管理用
user.uname はカードの利用者(所有者)名が漢字等で記録される。
user.uruby はカードの利用者(所有者)名のふりがなが記録される。
user.stdate は利用者(所有者)の有効期間の開始日時が記録される。管理用(このページでは未使用)
user.endate は利用者(所有者)の有効期間の終了日時が記録される。管理用(このページでは未使用)
user.flag は利用者(所有者)が有効(1)か無効(0)かのフラグ。管理用
junk.nid は未登録のカードのNFCID2が記録される。
junk.dtime は未登録のカードがかざされた時間が記録される。

$ mysql -u timemgr -pパスワード timerecorder < table.sql   ←テーブル作成

$ mysql -u timemgr -pパスワード timerecorder   ←timerecorderデータベースに入る

mysql> insert into user (uname,uruby) values('山田太郎','やまだたろう');  ←利用者登録
mysql> insert into card (nid) values('0000000000000000');     ←カード登録 (NFCID2を登録)
mysql> quit;

ユーザーを登録してからカード登録。
cardテーブルのuid(ユーザーID)をNOT NULLにしているのでユーザー無しのカード登録はできない(という前提)、基本的にはユーザーを登録してからカードを登録するという流れになる。もちろんダミーのユーザーIDを登録するなら順序は関係ない。
管理ツールを作成して会社で購入したカードを登録しておくなら最初に全てのカードを管理者ユーザーの所有としてフラグで殺しておいて別ユーザーに割り当て変えをしてフラグを有効化するということになるかと。

ウェブサーバー側(PHPとMySQLが入っていること)

db_recorder.php
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<?php
header( 'Expires: Fri, 1 Jab 2010 00:00:00 GMT' );
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
header( 'Cache-Control: no-store, no-cache, must-revalidate' );
header( 'Cache-Control: post-check=0, pre-check=0', false );
header( 'Pragma: no-cache' );

$json_string = file_get_contents('php://input');
$data = json_decode($json_string);
if (json_last_error() != JSON_ERROR_NONE){
    echo "不正です";
    exit;
}
$nid = $data->nid;

$dsn = 'mysql:dbname=timerecorder;host=localhost;charset=utf8mb4';
$user = 'timemgr';         //MySQL用timemgrアカウント
$password = 'パスワード';    //MySQL用timemgrアカウントのパスワード
$conn = new PDO($dsn, $user, $password);
//$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$sql = "select * from card 
            where nid = '" . $nid . "' 
                and stdate < NOW() 
                and endate > NOW() 
                and flag = 1;";

$stmt = $conn->query($sql);
$results = $stmt->fetchall();
if (empty($results)){
    $sql = "insert into junk 
                (nid) 
                value 
                ('" . $nid . "');";

    $conn->exec($sql);
    $msg = "みとうろくカードです";
} else {
    foreach ($results as $row) {
    $uid = $row['uid'];
    }
    $sql = "SELECT * FROM user 
                where id = '" . $uid . "' 
                    and stdate < NOW() 
                    and endate > NOW() 
                    and flag = 1;";

    $stmta = $conn->query($sql);
    $resultsa = $stmta->fetchall();
    if (empty($resultsa)){
        $msg = "ユーザーみとうろく";
    } else {
        //以下有効カードの場合の処理
        foreach ($resultsa as $rowa) {
            $uname = $rowa['uname'];
            $uruby = $rowa['uruby'];
        }
        //タイムログ処理
        //nidの最終記録を確認
        $sql = "select * from timelog 
                    where nid = '" . $nid . "' 
                        and dtime < NOW() 
                        and flag = 1 
                    order by dtime desc limit 1;";

        $stmta = $conn->query($sql);
        $resultsa = $stmta->fetchall();
        if (empty($resultsa)){
        $finout = 1; //記録が無ければ出勤=1から
            $dtime = '2000/01/01 00:00:00';
            $msg = $uruby . "さん、しゅっきん。";
        } else {
            foreach ($resultsa as $rowa) {
                $lid = $rowa['id'];
                $dtime = $rowa['dtime'];
                $finout = $rowa['finout']; //bitカラムの値の扱い要注意
            }
            if($finout == 1){ //直前の有効レコードが出勤
                $finout = 0;
                //前回の記録が出勤 日付をまたいでいたら警告
                if (mktime(0,0,0) > strtotime($dtime)){
                    $msg = $uruby . "さん、たいきん。しゅっきんがきのうです。かくにんしてください。";
                } else {
                    $msg = $uruby . "さん、たいきん";
                }
            } else { //直前の有効レコードが退勤
                $finout = 1;
                //前回の記録が退勤 それが本日なら警告
                if (strtotime($dtime) > strtotime(date("Y/m/d 00:00:00"))){
                    $msg = $uruby . "さん、しゅっきん。ぜんかいのたいきんがほんじつです。かくにんして下さい。";
                } else {
                    $msg = $uruby . "さん、しゅっきん。";
                }
            }
        }
        //最終記録が30秒以内の場合は最終を取り消し
        // さらに今回分も無効レコードとして登録(とにかく記録はする)
        if ((time() - strtotime($dtime)) < 30 ){
            $sql = "update timelog 
                        set flag = 0 
                            where id = $lid;";

            $conn->exec($sql);
            $sql = "insert into timelog 
                        (nid, finout, flag) 
                        value 
                        ('" . $nid . "', " . $finout . ", 0);";

            $conn->exec($sql);
            if ($finout = 0){
                $sfinout = "しゅっきん";
            } else {
                $sfinout = "たいきん";
            }
            $msg = $uruby. "さん、30秒いないの" . $sfinout . "、とりけし";
        } else {
            //記録を付ける
            $sql = "insert into timelog 
                        (nid, finout) 
                        value 
                        ('" . $nid . "', " . $finout . ");";

            $conn->exec($sql); 
        }
    }
}
unset($conn);
echo $msg;
?>

PHPでMySQLの操作はPDOを使用。
仕組みだけなのでエラー制御は無し。スマートさより何したいか解るように書いたのでトリッキーなコードも無し。凄い単純なのでネットワークの不通やデータベースとの接続失敗以外は殆どエラーらしいエラーが発生しないと思う。
NanoPiのOpen JTalkに読み上げさせるのでNanoPi側に返すメッセージ($msg)は基本ひらがなとする。ひらがな綴りを上手く読めない(イントネーションがおかしくなる)場合は逆にそこだけ漢字にする。またはイントネーション的に適切になるよう違う漢字を当てる(例: 漢字→幹事)

NanoPi NEO2側

speaking_card_recorder.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/sh
HOSTPATH=example.com/timerecorder/db_recorder.php
LOGFILE=/var/log/card.log
TMP=/tmp/tmp.wav

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
        RESP=`curl http://$HOSTPATH -s -X POST -H "Content-Type: application/json" -d '{"nid": "'$nid'"}' 2&>/dev/null`
        echo heartbeat > /sys/class/leds/nanopi:blue:status/trigger
        echo $RESP
        echo $RESP | open_jtalk -m /usr/share/hts-voice/mei/mei_normal.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow $TMP && aplay --quiet $TMP
        rm -f $TMP
        echo none > /sys/class/leds/nanopi:blue:status/trigger
        sleep 1
    fi
done

NanoPi NEO2側は基本的には前回から変わらない。サーバーから返された文字列をOpen JTalkで読み上げる処理が増えただけ。
OpenJTalkでの読み上げについてはNanoPi NEOがTwitterを声でツブヤクンデスを参照。このページにあるMei(女性声)まで入れる。

前回はカード読み取り後2秒は次のカードを読まないようにしていたが、今回はOpen JTalkが喋る間は次のカードを読み取らないようにしている。
読み上げバッチを裏で動かす方法も考えたが、読み上げ中に次の読み上げが発生するのが嫌だった。その分、次の利用者がカード読み込みを待たされることにはなるけど。

サンプル


「山田太郎さん、出勤」
普通に朝に出勤してきてカードをカードリーダーにかざすとこのようになる筈。

「田中一郎さん、退勤」
普通に(朝の)出勤の次にカードをカードリーダーにかざすとこのようになる筈。

「田中一郎さん、出勤。前回の退勤が本日です。確認して下さい。」
本日既に退勤しているのに再び出勤。ありえなくはないシチュエーションだが、一応確認を促す。再出勤が間違いなら30秒以内にもう一度カードをかざすと取り消される。

「山田太郎さん、30秒以内の "退勤" 取り消し。」
これは退勤するつもりが無いのに間違ってカードをかざして退勤になってしまったので30秒以内に再度カードをかざしたときのメッセージ。直前の "退勤" が取り消された(無効フラグ)。もちろん取り消し用の2回めのカードかざしも記録はするが無効フラグ。

「山田太郎さん、退勤。出勤が昨日です。確認して下さい。」
これは残業で日付が変わったなどでありえなくはないシチュエーションだが、昨日の退勤時にカードをかざし忘れた可能性を警告している。

利用者が警告メッセージを聞いて、本当に間違いであれば早急に管理者にデータを修正して貰うという運用を想定している。管理者がデータを修正するとか登録するとか記録されたデータを活用するためのアプリは自分で作ってねと。プログラムできなくてもAccessとかあれば簡単よ。

関連記事:

Up