[JAVA] Lisez le solde IC de votre carte d'étudiant (Felica) sur Android

introduction

Cet article est l'article du 8ème jour du SLP KBIT Advent Calendar 2019.

Récemment, j'ai appris que vous pouvez lire les données sur la carte IC en utilisant la fonction NFC d'Android. Alors j'ai pensé: «Puis-je lire ma carte d'étudiant?», Alors j'ai essayé.

À propos des données à lire

Cette fois, j'aimerais lire le solde IC de la coopérative universitaire stocké dans la carte d'étudiant.

La carte d'étudiant dont vous disposez est une carte IC compatible avec Felica, vous pouvez la lire avec un terminal compatible avec NFC Felica. La norme Felica est résumée dans Extrait du manuel d'utilisation de la carte FeliCa.

En ce qui concerne la structure des données de la carte d'identité d'étudiant, elle a été résumée dans Spécifications de la coopérative universitaire Felica, alors j'y ai fait référence.

Les données que je veux obtenir cette fois sont

--Code système ** 0xFE00 ** --Code de service ** 0x50D7 **

Il est stocké sous le nom de ** Purse Service ** dans. Selon les spécifications du Purse Service, le solde réel est stocké dans les 4 octets supérieurs au format little-toilen. Par conséquent, il est nécessaire d'extraire 4 octets après l'acquisition des données et de les convertir au format big endian.

En gros, il a la structure suivante.

- System 0:
    - ...
- System 1: (0xFE00)
    - IDm
    - Area
    - ...
    - Area
        - Service
        - ...
        - Purse Service: (0x50D7) <-ici

Lire Felica sur Android

Le processus global est MainActivity.java, Le processus de récupération des données de Felica se fait avec NfcReader.java.

Pour le traitement effectué par MainActivity.java, reportez-vous à la page suivante. [Android] Lisez NFC: Artisanat et courses de chevaux --Livedoor

La valeur unique de la carte est rendue constante au cas où vous voudriez lire une autre carte ou des données.

MainActivity.java



import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.widget.Toast;

import java.nio.ByteBuffer;


public class MainActivity extends AppCompatActivity {

    //constant
    private final byte[] TARGET_SYSTEM_CODE = new byte[]{(byte) 0xFE, (byte) 0x00};
    private final byte[] TARGET_SERVICE_CODE = new byte[]{(byte) 0x50, (byte) 0xD7};
    private final int TARGET_SIZE = 1;

    //Variables de manipulation des adaptateurs
    private NfcAdapter mNfcAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Obtenez une instance de l'adaptateur
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
    }

    @Override
    protected void onResume() {

        super.onResume();

        //Paramètres lorsque le NFC est suspendu
        Intent intent = new Intent(this, this.getClass());
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

        //Évitez d'ouvrir d'autres applications
        PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
        mNfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);

    }

    @Override
    protected void onPause() {
        super.onPause();

        //Ne pas recevoir lorsque l'activité est en arrière-plan
        mNfcAdapter.disableForegroundDispatch(this);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        NfcReader nfcReader = new NfcReader();
        super.onNewIntent(intent);

        //Obtenez NFC TAG
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

        //Lire les données de balance
        byte[][] data = nfcReader.readTag(tag, TARGET_SYSTEM_CODE, TARGET_SERVICE_CODE, TARGET_SIZE);

        //S'il ne peut pas être lu, il se termine
        if (data == null) {
            Toast.makeText(this, "error", Toast.LENGTH_SHORT).show();
            return;
        }

        //conversion
        byte[] balanceData = new byte[]{data[0][3], data[0][2], data[0][1], data[0][0]};
        int balance = ByteBuffer.wrap(balanceData).getInt();

        //afficher
        Toast.makeText(this, balance + "Cercle", Toast.LENGTH_LONG).show();
    }
}

Ensuite, créez NfcReader.java. NfcReader.java est basé sur le code de la page suivante. Obtention des données de bloc Felica (NFC) sur Android

Le contenu du traitement est presque le même, mais la méthode readTag a été modifiée pour que la valeur unique de la carte puisse être passée en argument.

NfcReader.java



import android.nfc.Tag;
import android.nfc.tech.NfcF;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;

import static android.content.ContentValues.TAG;

class NfcReader {

    public byte[][] readTag(Tag tag, byte[] targetSystemCode, byte[] targetServiceCode, int size) {
        NfcF nfc = NfcF.get(tag);
        if (nfc == null) {
            return null;
        }
        try {
            nfc.connect();

            //Créer une commande d'interrogation
            byte[] polling = polling(targetSystemCode);

            //Envoyez une commande et obtenez le résultat
            byte[] pollingRes = nfc.transceive(polling);

            //Obtenir l'ID système 0(Le premier octet est la taille des données, le deuxième octet est le code de réponse et la taille IDm est de 8 octets.)
            byte[] targetIDm = Arrays.copyOfRange(pollingRes, 2, 10);

            //Commande Créer une lecture sans chiffrement
            byte[] req = readWithoutEncryption(targetIDm, size, targetServiceCode);

            //Envoyez une commande et obtenez le résultat
            byte[] res = nfc.transceive(req);

            nfc.close();

            //Analysez le résultat et obtenez uniquement les données
            return parse(res);
        } catch (Exception e) {
            Log.e(TAG, e.getMessage() , e);
        }
        return null;
    }

    /*
     *Obtenez la commande d'interrogation.
     * @param systemCode byte[]Code système à spécifier
     * @return Commande d'interrogation
     * @throws IOException
     */
    private byte[] polling(byte[] systemCode) {
        ByteArrayOutputStream bout = new ByteArrayOutputStream(100);

        bout.write(0x00);           //Dummy de longueur de données
        bout.write(0x00);           //Code de commande
        bout.write(systemCode[0]);  // systemCode
        bout.write(systemCode[1]);  // systemCode
        bout.write(0x01);           //Code requis
        bout.write(0x0f);           //Créneau horaire

        byte[] msg = bout.toByteArray();
        msg[0] = (byte) msg.length; //Le premier octet est la longueur des données
        return msg;
    }

    /*
     *Obtenez la commande Lire sans chiffrement.
     * @param IDm ID du système spécifié
     * @param size Nombre de données à récupérer
     * @return Read Without Encryption, commande
     * @throws IOException
     */
    private byte[] readWithoutEncryption(byte[] idm, int size, byte[] serviceCode) throws IOException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream(100);

        bout.write(0);              //Dummy de longueur de données
        bout.write(0x06);           //Code de commande
        bout.write(idm);            // IDm 8byte
        bout.write(1);              //Numéro d'ancienneté(Les 2 octets suivants se répètent pendant ce nombre de minutes)

        //Étant donné que le code de service est spécifié comme Little Endian, spécifiez-le à partir de l'octet inférieur.
        bout.write(serviceCode[1]); //Code de service octet de poids faible
        bout.write(serviceCode[0]); //Code de service octet de poids fort
        bout.write(size);           //Nombre de blocs

        //Spécifier le numéro de bloc
        for (int i = 0; i < size; i++) {
            bout.write(0x80);       //Élément de bloc octet supérieur "Extrait du manuel d'utilisation Felica" 4.Voir l'élément 3
            bout.write(i);          //Numéro de bloc
        }

        byte[] msg = bout.toByteArray();
        msg[0] = (byte) msg.length; //Le premier octet est la longueur des données
        return msg;
    }

    /*
     *Lire sans analyse de réponse de chiffrement.
     * @param res byte[]
     * @retourne la représentation sous forme de chaîne
     * @throws Exception
     */
    private byte[][] parse(byte[] res) {
        // res[10]Code d'erreur. 0x00 est normal
        if (res[10] != 0x00) {
            throw new RuntimeException("Read Without Encryption Command Error");
        }

        // res[12]Nombre de blocs de réponse
        // res[13 + n * 16]Données réelles 16(byte/bloquer)Répéter
        int size = res[12];
        byte[][] data = new byte[size][16];
        for (int i = 0; i < size; i++) {
            byte[] tmp = new byte[16];
            int offset = 13 + i * 16;
            for (int j = 0; j < 16; j++) {
                tmp[j] = res[offset + j];
            }

            data[i] = tmp;
        }
        return data;
    }
}

J'ai essayé de l'utiliser

Lorsque je tenais ma carte d'étudiant sur mon smartphone, le solde était affiché avec précision. Il correspondait même par rapport au reçu. C'était bien.

en conclusion

C'était la première fois que je développais sur Android, mais c'était plus intéressant que ce à quoi je m'attendais. Android a diverses fonctions, donc je voulais essayer diverses choses si j'avais le temps.

Recommended Posts

Lisez le solde IC de votre carte d'étudiant (Felica) sur Android
Lisez et voyez les informations sur la carte FeliCa (WAON)
À propos de la troncature de String par le nombre d'octets sur Android
(Ruby on Rails6) Affichage de la base de données qui a obtenu l'identifiant de la base de données
Obtenez l'accélération et l'orientation du système de coordonnées mondial sur Android