射频识别(RFID)技术

读写操作(Android)

译者:陈广 日期:2020-6-26


在上一篇文章中,我们已经安装了 ST25 SDK,今天可以使用这个开发包来实现一些较为深入的功能。首先实现读取 UID 以及内存容量的功能。本文在上篇文章写的程序的基础上继续。

读取 UID 及内存容量

首先更改主窗体,添加几个显示数据的 TextView。打开 activity_main.xml 文件,更改代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/tagNameLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:orientation="horizontal">
        android:orientation="horizontal">
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="标签名称"
            android:textSize="15dp"
            android:textStyle="bold"
            android:typeface="serif"
            android:layout_weight="1"/>
        <TextView
            android:id="@+id/tv_tagName"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text=""
            android:textSize="13dp"
            android:typeface="serif"
            android:layout_weight="1"/>
    </LinearLayout>
    <LinearLayout
        android:id="@+id/uidLayout"
        android:layout_below="@+id/tagNameLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="UID"
            android:textSize="15dp"
            android:textStyle="bold"
            android:typeface="serif"
            android:layout_weight="1"/>
        <TextView
            android:id="@+id/tv_uid"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text=""
            android:textSize="13dp"
            android:typeface="serif"
            android:layout_weight="1"/>
    </LinearLayout>
    <LinearLayout
        android:id="@+id/tagMemSizeLayout"
        android:layout_below="@+id/uidLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="内存尺寸"
            android:textSize="15dp"
            android:textStyle="bold"
            android:typeface="serif"
            android:layout_weight="1"/>
        <TextView
            android:id="@+id/tv_tagMemSize"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text=""
            android:textSize="13dp"
            android:typeface="serif"
            android:layout_weight="1"/>
    </LinearLayout>
</RelativeLayout>

接下来更改 MainActivity.java 文件中onTagDiscoveryCompleted方法的代码:

@Override
public void onTagDiscoveryCompleted(NFCTag nfcTag, TagHelper.ProductID productId, STException e) {
    TextView tvTagName = findViewById(R.id.tv_tagName);
    TextView tvUID = findViewById(R.id.tv_uid);
    TextView tvMemSize = findViewById(R.id.tv_tagMemSize);
    if (nfcTag != null) {
        tvTagName.setText(nfcTag.getName()); //获取标签名称
        try {
            tvUID.setText(nfcTag.getUidString()); //获取标签UID
        } catch (STException ex) {
            tvUID.setText(ex.getMessage());
        }

        try { //获取标签内存尺寸
            tvMemSize.setText(Integer.toString(nfcTag.getMemSizeInBytes()));
        } catch (STException ex) {
            tvMemSize.setText(ex.getMessage());
        }
    } else {
        tvTagName.setText("");
        tvUID.setText("");
        tvMemSize.setText("");
        Toast.makeText(this, "标签发现失败", Toast.LENGTH_LONG).show();
    }
}

运行效果如下图所示:

图 1:运行结果

看来我之前使用单片机读 UID 时显示的字节顺序正好反了。此开发包只能读取 NFC 标签的内存尺寸,在读普通 15693 内存尺寸时会出错。当然,在正式写程序时,我们应当限制标签类型,只让 ST25DV 标签触发事件。

读与操作

接下来演示如何读写数据。估计 ST25 SDK 的读写操作在后台是使用线程实现的,所以不能在 UI 线程中调用,所以写起来比较麻烦。Android 在多线程中访问 UI 的操作非常麻烦,远没有 C# 这么便捷。这一块我暂时还不想深入研究,索性抄现成的代码再改改的能力还是有的,花点时间还是写完了 Demo。

UI 设计

首先将 activity_main.xml 的代码更改如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/tagNameLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:orientation="horizontal">
        android:orientation="horizontal">
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="标签名称"
            android:textSize="15dp"
            android:textStyle="bold"
            android:typeface="serif"
            android:layout_weight="1"/>
        <TextView
            android:id="@+id/tv_tagName"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text=""
            android:textSize="13dp"
            android:typeface="serif"
            android:layout_weight="1"/>
    </LinearLayout>
    <LinearLayout
        android:id="@+id/uidLayout"
        android:layout_below="@+id/tagNameLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="UID"
            android:textSize="15dp"
            android:textStyle="bold"
            android:typeface="serif"
            android:layout_weight="1"/>
        <TextView
            android:id="@+id/tv_uid"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text=""
            android:textSize="13dp"
            android:typeface="serif"
            android:layout_weight="1"/>
    </LinearLayout>
    <LinearLayout
        android:id="@+id/tagMemSizeLayout"
        android:layout_below="@+id/uidLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="内存尺寸"
            android:textSize="15dp"
            android:textStyle="bold"
            android:typeface="serif"
            android:layout_weight="1"/>
        <TextView
            android:id="@+id/tv_tagMemSize"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text=""
            android:textSize="13dp"
            android:typeface="serif"
            android:layout_weight="1"/>
    </LinearLayout>
    <Button
        android:id="@+id/btn_readBytes"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="读取前10个字节数据"
        android:layout_below="@+id/tagMemSizeLayout"
        android:layout_margin="10dp" />
    <TextView
        android:id="@+id/tv_tagData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_readBytes"
        android:text=""
        android:textSize="13dp"
        android:typeface="serif"/>
    <Button
        android:id="@+id/btn_writeBytes"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="写入10字节数据"
        android:layout_below="@+id/tv_tagData"
        android:layout_margin="10dp" />
    <Button
        android:id="@+id/btn_writeNdefMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="写入NDEF数据"
        android:layout_below="@+id/btn_writeBytes"
        android:layout_margin="10dp" />
</RelativeLayout>

这里,我们添加了三个按钮和一个 TextView,按钮分别用于读取10个字节,写入10个字节,写入 NDEF 消息;TextView 用于显示读出来的 10 个字节的数据。

更改 MainActivity.java 如下:

package com.example.st25;

import androidx.appcompat.app.AppCompatActivity;
import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.st.st25sdk.NFCTag;
import com.st.st25sdk.STException;
import com.st.st25sdk.TagHelper;
import com.st.st25sdk.ndef.NDEFMsg;
import com.st.st25sdk.ndef.UriRecord;
import java.util.Arrays;

import static com.st.st25sdk.ndef.UriRecord.NdefUriIdCode.NDEF_RTD_URI_ID_HTTP_WWW;

public class MainActivity extends AppCompatActivity implements TagDiscovery.onTagDiscoveryCompletedListener {

    static final String TAG = "MainActivity";
    private NfcAdapter nfcAdapter;
    // Last tag taped
    private NFCTag mNfcTag;
    TextView tvData;
    byte[] tagDatas;

    enum Action {
        WRITE_NDEF_MESSAGE,
        READ_BYTES,
        WRITE_BYTES
    };

    enum ActionStatus {
        ACTION_SUCCESSFUL,
        ACTION_FAILED,
        TAG_NOT_IN_THE_FIELD
    };

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

        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        //写入NDEF消息按钮
        Button writeNdefMessageButton = (Button) findViewById(R.id.btn_writeNdefMessage);
        writeNdefMessageButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mNfcTag != null) {
                    executeAsynchronousAction(Action.WRITE_NDEF_MESSAGE);
                }
            }
        });
        //读取字节按钮
        Button readBytesButton = (Button) findViewById(R.id.btn_readBytes);
        readBytesButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mNfcTag != null) {
                    executeAsynchronousAction(Action.READ_BYTES);
                }
            }
        });
        //写入字节按钮
        Button writeBytesButton = (Button) findViewById(R.id.btn_writeBytes);
        writeBytesButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mNfcTag != null) {
                    executeAsynchronousAction(Action.WRITE_BYTES);
                }
            }
        });
    }

    @Override //页面失去焦点
    protected void onPause() {
        super.onPause();
        if (nfcAdapter != null) {
            //当前应用程序不在前台时,NFC事件将不再发送到当前应用程序。
            nfcAdapter.disableForegroundDispatch(this);
        }
    }

    @Override //页面获取焦点
    protected void onResume() {
        super.onResume();

        if (nfcAdapter == null) {
            Toast.makeText(this, "对不起,您的设备不支持NFC功能!", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }
        if (!nfcAdapter.isEnabled()) {
            Toast.makeText(this, "请在系统设置中开启NFC功能!", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent(this,
                getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        //将 NFC 事件发送至当前应用程序
        nfcAdapter.enableForegroundDispatch(this, pi, null, null);

        Intent intent = getIntent();
        String action = intent.getAction();
        Tag nfcTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        if (nfcTag != null) {
            new TagDiscovery(this).execute(nfcTag);
        }
    }

    @Override //当前app正在前端界面运行,这个时候有intent发送过来,那么系统就会调用onNewIntent回调方法
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        setIntent(intent);
    }

    @Override
    public void onTagDiscoveryCompleted(NFCTag nfcTag, TagHelper.ProductID productId, STException e) {
        TextView tvTagName = findViewById(R.id.tv_tagName);
        TextView tvUID = findViewById(R.id.tv_uid);
        TextView tvMemSize = findViewById(R.id.tv_tagMemSize);
        tvData = findViewById(R.id.tv_tagData);
        if (nfcTag != null) {
            mNfcTag = nfcTag;
            tvTagName.setText(nfcTag.getName()); //获取标签名称
            try {
                tvUID.setText(nfcTag.getUidString()); //获取标签UID
            } catch (STException ex) {
                tvUID.setText(ex.getMessage());
            }
            //获取标签内存尺寸
            try {
                tvMemSize.setText(Integer.toString(nfcTag.getMemSizeInBytes()));
            } catch (STException ex) {
                tvMemSize.setText(ex.getMessage());
            }
            tvData.setText("");
        } else {
            tvTagName.setText("");
            tvUID.setText("");
            tvMemSize.setText("");
            tvData.setText("");
            Toast.makeText(this, "标签发现失败", Toast.LENGTH_LONG).show();
        }
    }

    private void executeAsynchronousAction(Action action) {
        Log.d(TAG, "开始后台操作" + action);
        new myAsyncTask(action).execute();
    }

    //异步操作类
    private class myAsyncTask extends AsyncTask<Void, Void, ActionStatus> {
        Action mAction;
        int memSizeInBytes;

        public myAsyncTask(Action action) {
            mAction = action;
        }

        @Override //开始异步操作
        protected ActionStatus doInBackground(Void... param) {
            ActionStatus result;
            try {
                switch (mAction) {
                    case WRITE_NDEF_MESSAGE:
                        NDEFMsg ndefMsg = new NDEFMsg();// 创建一个 NDEF 消息
                        UriRecord uriRecord = new UriRecord(NDEF_RTD_URI_ID_HTTP_WWW, "iotxfd.cn");
                        ndefMsg.addRecord(uriRecord);
                        mNfcTag.writeNdefMessage(ndefMsg); //写入 NDEF 消息
                        result = ActionStatus.ACTION_SUCCESSFUL;
                        break;
                    case READ_BYTES:
                        tagDatas = mNfcTag.readBytes(0, 10); //读取从0地址开始的10个字节
                        result = ActionStatus.ACTION_SUCCESSFUL;
                        break;
                    case WRITE_BYTES:
                        byte[] wdatas = {61, 62, 63, 64, 65, 66, 67, 68, 69, 70};
                        mNfcTag.writeBytes(0, wdatas); //从0地址开始写入10个字节
                        result = ActionStatus.ACTION_SUCCESSFUL;
                        break;
                    default:
                        result = ActionStatus.ACTION_FAILED;
                        break;
                }

            } catch (STException e) {
                switch (e.getError()) {
                    case TAG_NOT_IN_THE_FIELD:
                        result = ActionStatus.TAG_NOT_IN_THE_FIELD;
                        break;
                    default:
                        e.printStackTrace();
                        result = ActionStatus.ACTION_FAILED;
                        break;
                }
            }
            return result;
        }

        @Override //异步操作结束后,显示结果,UI的访问应放在这里进行
        protected void onPostExecute(ActionStatus actionStatus) {

            switch (actionStatus) {
                case ACTION_SUCCESSFUL:
                    switch (mAction) {
                        case WRITE_NDEF_MESSAGE:
                            Toast.makeText(MainActivity.this, "成功写入NDEF消息", Toast.LENGTH_LONG).show();
                            break;
                        case READ_BYTES:
                            tvData.setText(Arrays.toString(tagDatas));
                            break;
                        case WRITE_BYTES:
                            Toast.makeText(MainActivity.this, "成功写入10个字节", Toast.LENGTH_LONG).show();
                    }
                    break;
                case ACTION_FAILED:
                    Toast.makeText(MainActivity.this, "操作失败", Toast.LENGTH_LONG).show();
                    break;
                case TAG_NOT_IN_THE_FIELD:
                    Toast.makeText(MainActivity.this, "标签不在感应区域!", Toast.LENGTH_LONG).show();
                    break;
            }
            return;
        }
    }
}

运行效果:

图 2:运行结果

先操作读取和写入字节,这个不会有什么问题。关键是写入 NDEF 数据,将此数据写入淘宝购买的普通 NFC 标签后,退出程序,然后再次贴近 NFC 标签,则会自动打开指定网站。但是如果写入的是 ST25DV64K,虽然可以成功写入,但无法自动打开网站。我估计如果要写入 NDEF 消息,需要先进行 NDEF 格式化,淘宝买来的标签是预先格式化好的,因为出厂时,里面的数据并不是 0。这块之后再深入研究。

;

© 2018 - IOT小分队文章发布系统 v0.3