Closed15

カードリーダーを使って入退室管理がしたい

しょーたしょーた

【背景】

私がお世話になっているコワーキングスペースではドロップインの料金が滞在時間によって大きく3つの区分に分かれている。で精算の際にお客さんが何時間滞在していたか計算するわけだが、時間の計算などは人力で行われている。時間計算や料金判定などをシステム化することで、お客さんの待っている時間短縮&スタッフの方の負担軽減をできればなぁと考えた。

【したいこと】

ドロップインの入室時間と退室時間をシステムによって計算できるようにし、計算結果から料金表示までを行いたい。

【制約】

  • がっつりアプリ開発となるといろいろコストもかかるため、今回はできるだけコストをかけずに実現したい。
  • 操作方法は単純なものにしたい。
  • 「このシステム使うより自分たちで計算したほうが早いやん…」とならないように、処理速度なども意識

ざっくりだが、概要は上記の内容で進める。
次は詳細の仕様などを決めていく。

しょーたしょーた

【前提条件】

  • すでにあるものなどを活用していく。
  • ITがあまり強くない方も使う可能性があるので、PCでの入力などは極力減らす

今あるものとして「会員カード」があったのでそれに注目してみる。
現在の会員カードはドロップインではなく月額会員さん用のもので、月額会員さんはそのカードを使ってすでに導入済みの別アプリを使って入退室を管理している。ただそのアプリに機能追加するなどはできない。
そのカードはFelicaと呼ばれるタイプのカードでSuicaなどと同じようなカード。カードにはそれぞれ固有IDが付与されているみたい。

なのでシステム構成として以下で進めてみる。

絶対詰めきれていない仕様とかあると思うがざっくりこんな感じで進める。

しょーたしょーた

早速フロント側から実装開始。
今回もNext.js TypeScriptで実装を進めようと思う。正直HTML・Jsとかの構成でも作れるが勉強も兼ねてこの構成とした。

それぞれのバージョンは以下の通り

  • Next.js 13.3.0
  • TypeScript 5.0.4
  • React 18.2.0
  • TailwindCSS 3.3.1

でNext.jsについてはpagesフォルダ管理→appフォルダ管理の構成に変更した。また今回はTailwindCSSを導入してみた。

しょーたしょーた

フロント側のすることリスト

  1. 環境構築
  2. ブラウザからのカードリーダー接続
  3. カードリーダーからカード情報の読み取り(固有IDなど)
  4. フロントエンドを整える
  5. カードリーダーからカード情報を読み取ったタイミングの時間を取得
  6. 取得した時間から入退室の時間差分を計算
  7. 計算結果から料金を確定。表示
  8. その日のデータをバックエンドに送信

大まかにはこんな流れになるのかな。

しょーたしょーた

まずはカードリーダーをブラウザから起動させるところ。

調べるとnavigator.usb.requestDevice()という記載でUSBデバイスの検出ができるみたい。
なのでonClick内の関数にこちらを記載してみた。

page.tsx
    const onClickCardReaderActive = async () => {
        try {
            const device = await navigator.usb.requestDevice();
            console.log(device);
        } catch (error) {
            console.log(error);
        }
    };

    return (
        <>
            <div
                onClick={onClickCardReaderActive}
                id="card-reader-active-button"
                className="cursor-pointer"
            >
                カードリーダー起動確認
            </div>
        </>
    );

…………………上手くいかない。(そんな気はしていた)
エラー内容はこちら

Failed to execute 'requestDevice' on 'USB': 1 argument required, but only 0 present.

どうやらrequestDeviceにはfilitersという引数があるらしく、それを設定しなければいけないようだ。

しょーたしょーた

引数のfiltersには

  • vendorId
  • productId
  • classCode
  • subclassCode
  • protocolCode

というのが設定できるとのこと。それぞれの設定項目の意味としては以下の通り。

  • vendorId: USBデバイスのメーカーIDを示す16ビットの整数値です。メーカーによって割り当てられた固有の番号であり、同じメーカーのUSBデバイスには同じvendorIdが割り当てられます。
  • productId: USBデバイスの製品IDを示す16ビットの整数値です。メーカーが割り当てた固有の番号であり、同じメーカーの異なるUSBデバイスには異なるproductIdが割り当てられます。
  • classCode: USBデバイスのクラスを示す8ビットの整数値です。USB仕様で定義された標準的なクラスがあり、USBデバイスはそれぞれのクラスに割り当てられます。
  • subclassCode: USBデバイスのサブクラスを示す8ビットの整数値です。classCodeに対応するサブクラスがある場合に使用されます。
  • protocolCode: USBデバイスのプロトコルを示す8ビットの整数値です。classCodeとsubclassCodeに対応するプロトコルがある場合に使用されます。
    (chatGPTより)

とのことなので、それぞれ値を入れて再度チャレンジ。

 vendorId: 0x054c, // Sony Corporation
productId: 0x032c, // PaSoRi RC-S300/P
classCode: 0xff,
subclassCode: 0x01,
protocolCode: 0x01,

少し進んで、

こんなポップアップが出るようになったが、肝心のデバイスが検出されていない。
他のサイトでは問題なく検出されているためPC⇄カードリーダー間は問題ないと思われる。
原因を調査する。

しょーたしょーた

とここで一つの疑問が。

これローカル環境だからなのでは…

と。
サーバーにアップすれば問題なく動くのではと思い、一旦今の状態でサーバーアップを試みることにした。
サーバーについてはひとまず自分が契約しているXサーバーでいっか!と思ったが、Next.jsってどうやってアップロードするの……??

調べる。

しょーたしょーた

こちらの記事でNext.js&microCMS環境でのデプロイをXサーバーでやっているものがあったので参考にさせてもらう。
https://blog.masizime.com/microcms-nextjs-deploy-ftp.html

最初に出てきたのが
「GitHub Actionsを使う必要があります」とのこと。
ほう…Next.jsのデプロイにはそういうのがいるのか…

このプロジェクトはGitHubでの管理はすでにやっている。ということで少し脱線するがGitHub Actionsの設定を行う(これはいつかやってみたいと思っていたしこれも勉強)

しょーたしょーた

ひとまず先ほどの記事に従い、.github/workflows/deploy-ftp.ymlというファイルを作る。
中身のファイルもそのままそっくりコピーさせてもらった。

name: Deploy FTP CI

on:
  push:
    branches: [ main ]
  repository_dispatch:
    branches: [ main ]
  workflow_dispatch:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16.x]

    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm run build --if-present
      - run: npm run export
      - name: Deploy FTP
        uses: SamKirkland/FTP-Deploy-Action@4.2.0
        with:
          server: ${{ secrets.FTP_SERVER }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          protocol: ${{ secrets.FTP_PROTOCOL }}
          port: ${{ secrets.FTP_PORT}}
          server-dir: ${{ secrets.FTP_REMOTE_ROOT }}
          local-dir: ./out/
          dangerous-clean-slate: true
しょーたしょーた

この時点ではブランチを切って作業していたので作業ブランチからmainブランチへマージして、GitHubのActionsを見たところなんか動き始めていた。

おぉーこれがGitHub Actionsかーと感心していたのも束の間、すぐにエラーで停止。
まぁこれは想定通りで、serverとか変数にしているけどどこにも定義していないので。
次はこれを定義する。定義するのはGitHubのSettings→Secrets and variables→Actions
ここでFTP_SERVERなどの変数名とその内容を登録する。

でFTP接続するときにprotocolとかportとかって特に設定したことないので、ここは一旦削除した。
なので設定したのは以下の4つ

  • FTP_SERVER
  • FTP_USERNAME
  • FTP_PASSWORD
  • FTP_REMOTE_ROOT

これで再チャレンジするも再度エラー。次はnpm ciのところでエラーになっていた。
どうもpackage.jsonとpackage-lock.jsonにズレがある時npm ciはエラーになるとのことなので、これの前にnpm installを追加した。

これで再チャレンジするも再度エラー。。次はnpm run exportのところ。
package.jsonにexportのことは書いてなかったので、追記した。

    "scripts": {
       ......
        "export": "next export"
    },

ちなみにこれはNext.jsのコードを静的なものにするコマンドのよう。

これGitHubに上げる前に、ローカル内で動くのか確認。すると再度エラー。。。
静的なものにするにはnext.config.jsに追加の設定がいるとのことなので追記。

next.config.js
const nextConfig = {
    experimental: {
        appDir: true,
    },
+     output: "export",
+    trailingSlash: true,
};

module.exports = nextConfig;

これで試すとnext exportはできた。
ただnext.jsではoutput: "export"の設定をするとbuildコマンドでexportまでしてくれるとのこと。
なのでGitHub Acitons側もnpm run exportの記載は削除

そんなこんなで最終的にはこうなった

name: Deploy FTP CI

on:
  push:
    branches: [ main ]
  repository_dispatch:
    branches: [ main ]
  workflow_dispatch:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16.x]

    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm install
      - run: npm run build --if-present
      - name: Deploy FTP
        uses: SamKirkland/FTP-Deploy-Action@4.2.0
        with:
          server: ${{ secrets.FTP_SERVER }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          server-dir: ${{ secrets.FTP_REMOTE_ROOT }}
          local-dir: ./out/
          dangerous-clean-slate: true
しょーたしょーた

とここまでしてXサーバーにアップしたわけだが、肝心のUSBデバイスの検出は変わらずできなかった(悲しい)
となると設定の方がおかしいとみるべき。

怪しいのはfiltersのところ。それぞれのIDを設定しているわけだが、その設定が異なっているともちろんデバイスは検出されないとみた。なので一つずつ消していく。

まずはガツっと下から4つ全て消して、vendorIdだけにしてみた。
設定の意味としてはSony製のデバイスを検出する。ということになり範囲が広くなるかなとの想定。
これでローカル環境で見てみたところ……

なんやねーん。設定かーい

ということで今回の不具合は条件を絞りすぎていた(or 設定値を間違えていた)ことによるものでした。
ちなみにvendorIdとclassCodeの2つでも検出されたので、この数値は合っているみたい。
それ以外を入れると検出されなくなった。

しょーたしょーた

ということで無事USBデバイスの検出まではできたので次はいよいよカード情報の読みよりに進みます。

しょーたしょーた

ここが1番の鬼門というのは覚悟していたが、想像以上に何もできない…
うーんうーんと唸っていたところ神記事を発見した。
https://sakura-system.com/?p=2892

使っているリーダーも同じでコードも細かく書いてある!(しかも記事が2022年で最近!)
さらにはJsで書いているということで、もうこの記事を書いた人にお礼の品を持っていきたいくらい。
将来的にこんな記事描けるようになりたいなぁとも思った。

こちらを参考にまずはカードリーダーから何かしらの情報をもらう(もしくは送る)というところまでの実装をしていきたい。

しょーたしょーた

まず初めにテストデータを送信してそれに対するレスポンスが返ってくるかを試した。

このスクラップは2ヶ月前にクローズされました