学生証を利用した記録アプリ

# AndroidでNFC学生証を使った集計アプリを作ってみた

## はじめに
この記事は、Androidスマートフォンを使って学生証のNFC（Felica）を読み取り、学籍番号を抽出してメール送信や記録を行うアプリについて紹介します。入部手続きの簡略化やイベントでの集金記録などを目的に作成したので忘れないようにメモとして書きます。

## 作ったもの
- 学生証のFelicaチップから学籍番号を読み取るAndroidアプリ
- 読み取った学籍番号に基づいて自動でメールを送信する機能
- 操作履歴やエラーをリアルタイムで表示するログ機能

## なぜ作ったのか
2025年度の部長になり初めての新歓を迎えました。
しかし、あまりに入部希望者が多いとその分1人1人にDiscordのURLを共有したり入部希望者の連絡先を記録するのに手間取ってしまいました。また、いくらURLの共有のためであっても新入生の連絡先を聞きまくるのは周りの目を気にするといかがなものかと考えてしまいどうにか個人のSNSを利用せずに共有する手法はないかと考えた末、学生全員が所持している学番メールと学生証を使って手続きを進めたいと考えました。また、同時に昨年度まで飲み会幹事を行っていたのですがその際に誰からお金を受け取ったのかの記録をつけるのに学生証をPaSoRiにかざしてもらい最初記録を取っていましたがお店にてノートパソコンとPaSoriを片手に回るのはとても恥ずかしいものでした。そこでスマホ1台で完結させられないかと考えたのもきっかです。

## 開発環境・ツール
開発には以下の環境とツールを使用しました。
- **IDE**: Android Studio
- **言語**: Kotlin
- **テスト**: Androidデバイス実機 (NFC機能付き)


## システム概要
### 機能一覧
- NFC Felica読み取り (学生証から学籍番号を抽出)
- 自動メール送信 (指定ドメインのメールアドレスへ)
- リアルタイムログ表示

### 使用技術
- **NFC**: Felica (こちらの番号は一般的な大学の学生証のコードです。)
    - システムコード: `0xFE00`
    - サービスコード: `0x1A8B`
    - 文字エンコーディング: Shift_JIS
- **メール送信**:
    - SMTPサーバー: Gmail SMTP (`smtp.gmail.com:587`)
    - 認証: アプリパスワード
- **開発言語**: Kotlin
- **Android API Level**: 21以上

### プロジェクト構造
```
app/
├── src/main/
│   ├── java/com/example/studentcardsystem/
│   │   ├── MainActivity.kt           # メイン画面、NFC処理、メール送信起点
│   │   ├── FelicaReader.kt          # Felicaカード読み取りロジック
│   │   ├── EmailSender.kt           # メール送信処理
│   │   └── LogAdapter.kt            # ログ表示用アダプター
│   ├── res/
│   │   ├── layout/
│   │   │   ├── activity_main.xml
│   │   │   └── dialog_email_settings.xml
│   │   ├── xml/
│   │   │   └── nfc_tech_filter.xml
│   └── AndroidManifest.xml
├── build.gradle (Module :app)
build.gradle (Project)
```

## 実装のポイント
### 1. NFC (Felica) の読み取り (`FelicaReader.kt`)
- `NfcAdapter.ReaderCallback` を実装してNFCカード検出を処理。
- 指定のシステムコード (`0xFE00`) とサービスコード (`0x1A8B`) を使用してFelicaカードにアクセス。
- 読み取ったデータをShift_JISでデコードして学籍番号を抽出。
 
### 2. メール送信処理 (`EmailSender.kt`)
- `javax.mail` ライブラリを使用してSMTP経由でメールを送信。
- GmailのSMTPサーバーとアプリパスワードで認証。
- メール送信はバックグラウンドスレッド (`CoroutineScope(Dispatchers.IO)`) で実行し、UIのブロッキングを回避。
  
### 3. メイン処理とUI (`MainActivity.kt`)
- NFCタグ検出時のインテント処理。
- `FelicaReader` を使った学籍番号の読み取り。
- `EmailSender` を呼び出してメールを送信。
- SharedPreferencesを利用したメール設定の保存と読み込み。
- ログメッセージのUI表示更新。
  

## 使い方
1. アプリを起動。
   <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3860583/703f9b87-96b4-48f4-84a4-6659834242da.png" width="250" alt="アプリ起動画面">

2. 設定アイコンから、送信元Gmailアドレス、アプリパスワード、メールの件名・本文テンプレートを設定。
   <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3860583/5453ea61-b924-4e6b-ad7e-d1c41122cc4b.png" width="250" alt="設定画面">

   **注意:** Gmailでメールを送信するには、2段階認証プロセスを有効にし、**アプリパスワード**を生成して使用する必要があります。
3. スマートフォンのNFCが有効になっていることを確認。

4. 学生証をデバイスの背面にタッチ。(記録モード)
   <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3860583/0d289af6-b779-4f9b-a36a-d1bc4138ce7c.png" width="250" alt="NFC読み取り中">

5. 学籍番号が読み取られ、設定に基づいてメールが送信されます。
   <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3860583/48616879-c3e7-4795-b554-c82e1197e8f7.png" width="250" alt="メール送信完了">


## そもそもFelicaとは
### Felicaとは
Felica（フェリカ）は、ソニーが開発した非接触型ICカード技術で、日本の多くの学生証、交通系ICカード（Suica、PASMOなど）、電子マネー（楽天Edy、nanacoなど）に採用されています。

### 学生証のFelica仕様
一般的な大学の学生証では以下の仕様が使用されています：
- **システムコード**: `0xFE00`（共通領域）
- **サービスコード**: `0x1A8B`（学籍番号格納領域）
- **文字エンコーディング**: Shift_JIS

### Felicaの製造番号（IDm）について
FelicaカードにはIDm（8バイト）という固有の製造番号が割り当てられています：
- **IDm**: カード固有の識別子（8バイト、16進数表記）
- **用途**: カードの個体識別、重複チェックなど
- **特徴**: カード製造時に割り当てられ、変更不可
- **取得方法**: NFCタグ検出時に`tag.id`から取得可能

このアプリでは学籍番号とIDmの両方を記録することで、同一人物の重複エントリーを防止したり、カードの真正性確認に活用できます。

### Felicaの利点
- **普及率の高さ**: 交通系ICなどもこの規格です
- **セキュリティ**: データ通信が暗号化
- **読み取り速度**: 高速読み取り

### 注意点
- 大学によってシステムコードやサービスコードが異なる場合があります
- 学生証は読み取るぐらいにしておきましょう。間違っても偽装学生証を作るなどしないでくださいね。

## 今後
こちらのアプリを後々改造してGASなどと連携して部員管理のスプレッドシートと連携させて大学に提出する書類の作成も自動化させて他の部活に配布したりしたいです。それと早く弊学はモバイル学生証を発行してください。

## スペシャルサンクス
komihorium(部活の先輩)←学生証の仕組みについて入学時沢山教えてもらった。

---
GitHub→ ｇhttps://github.com/endresserror/Management_Member_By_StundetCard
とりあえず完成した瞬間に書いた記事なので今後詳細は追記していきます。