LoginSignup
0
2

More than 3 years have passed since last update.

WebUSBでType2Tagを読む

Posted at

備忘録です。

まずNFCについてですが、Near Field Communicationの略称で、近距離無線通信と呼ばれる技術です。

通信距離は10cm程度となっています。詳しい情報は、業界標準化団体NFCフォーラムの設立メンバーでもあるSONYさんのページから確認できます。

また、NFCにはタグと呼ばれる仕様があります。

現在、タグの定義には5種類あり、Type1~Type5までとなっております。

タイプによって、基づいている仕様が異なっていたり、通信速度やメモリ容量など形式が異なっています。

ちなみに、Type3はFeliCaと呼ばれるSONYさんが開発した技術方式に基づいています。

今回Type2Tagに絞っているのは、以下の製品を用いる事と、上記で説明した通り、タイプ毎に方式が異なってくるためです。

サンワサプライ NFCタグ(10枚入り) 白 MM-NFCT

また、当記事に載せるコードの大部分は下記のQiita記事を参考にしています。

WebUSBでFeliCaの一意なIDであるIDmを読む


コード全体は以下のようになります。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>NFC Test</title>
</head>
<body>
   <button id="start">PaSoRi Setup</button>
   <p id="idm"></p>
</body>
<script src="pasori.js"></script>
</html>
pasori.js
let startButton = document.getElementById('start');
let idmMessage = document.getElementById('idm');
async function sleep(msec) {
  return new Promise(resolve => setTimeout(resolve, msec));
}
async function send(device, data) {
  let uint8a = new Uint8Array(data);
  console.log(">>>>>>>>>>");
  console.log(uint8a);
  await device.transferOut(2, uint8a);
  await sleep(10);
}
async function receive(device, len) {
  console.log("<<<<<<<<<<" + len);
  let data = await device.transferIn(1, len);
  console.log(data);
  await sleep(10);
  let arr = [];
  for (let i = data.data.byteOffset; i < data.data.byteLength; i++) {
    arr.push(data.data.getUint8(i));
  }
  console.log(arr);
  return arr;
}

async function sendCommand (device, cmd, params) {
  let command = [0x00, 0x00, 0xff, 0xff, 0xff];
  let data = [0xd6, cmd].concat(params);
  command = command.concat([data.length, 0, 256 - data.length]);
  command = command.concat(data);
  let sum = 0;
  for (let i = 0; i < data.length; i++) {
    sum += data[i];
  }
  let parity = (256 - sum) % 256 + 256;
  command = command.concat([parity, 0]);
  await send(device, command);
  await receive(device, 6);
  const result = await receive(device, 40);
  return result;
}

async function session(device) {
  await send(device, [0x00, 0x00, 0xff, 0x00, 0xff, 0x00]);
  console.debug('GetProperty');
  await sendCommand(device, 0x2a, [0x01]);
  console.debug('SwitchRF');
  await sendCommand(device, 0x06, [0x00]);
  console.debug('InSetRF');
  await sendCommand(device, 0x00, [0x02, 0x03, 0x0f, 0x03]);
  console.debug('InSetProtocol');
  await sendCommand(device, 0x02, [0x00, 0x18, 0x01, 0x01, 0x02, 0x01, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x08, 0x08, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0e, 0x04, 0x0f, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x06]);
  await sendCommand(device, 0x02, [0x01, 0x00, 0x02, 0x00, 0x05, 0x01, 0x00, 0x06, 0x07, 0x07]);
  console.debug('InCommRF:SENS');
  await sendCommand(device, 0x04, [0x36, 0x01, 0x26]);
  console.debug('InCommRF:SDD');
  await sendCommand(device, 0x02, [0x04, 0x01, 0x07, 0x08]);
  await sendCommand(device, 0x02, [0x01, 0x00, 0x02, 0x00]);
  const ssdRes = await sendCommand(device, 0x04, [0x36, 0x01, 0x93, 0x20]);
  const result = arrayToHexString(ssdRes.slice(15, 19));
  console.log(result);
  return result;
}

function arrayToHexString (arr) {
  if(arr.length > 0) {
    let result = '';
    for (let i = 0; i < arr.length; i++) {
      const val = arr[i];
      if (val < 16) {
        result += '0';
      }
      result += val.toString(16);
      idmMessage.innerText = "カードのIDm: " + result;
    }
    return result.toUpperCase();
  } else {
    return null;
  }
}

startButton.addEventListener('click', async () => {
  let device;
  try {
    device = await navigator.usb.requestDevice({'filters': [
      {'vendorId': 0x054c, 'productId': 0x06c1}
    ]});
    console.log("open");
    await device.open();
  } catch (e) {
    console.log(e);
    alert(e);
    throw e;
  }
  try {
    console.log("selectConfiguration");
    await device.selectConfiguration(1);
    console.log("claimInterface");
    await device.claimInterface(0);
    console.log(device);
    do {
      await session(device);
      await sleep(500);
    } while (true);
  } catch (e) {
    console.log(e);
    alert(e);
    try {
      device.close();
    } catch (e) {
      console.log(e);
    }
    throw e;
  }
});

startというidを持つボタンをクリックした時、最初にデバイスのセットアップをします。

navigator.usb.requestDeviceメソッドを呼び出す事で、ブラウザからユーザーに対し利用するデバイスの選択を求められますが、この際filtersというオプションにベンダIDと製品IDを渡す事で利用するデバイスを絞る事ができます。

Macで自身に繋がっているUSBデバイスのベンダIDと製品IDを調べるには下記コマンドを実行してください。

$ system_profiler SPUSBDataType

この結果接続されているUSBデバイスが一覧で表示されます。その中からRC-S380/Sの項目を探しましょう。これがSONY製PaSoRiのデバイス名です。

...
        RC-S380/S:

          Product ID: 0x06c1
          Vendor ID: 0x054c  (Sony Corporation)
          Version: 1.11
          Serial Number: 0658444
          Speed: Up to 12 Mb/s
          Manufacturer: SONY
          Location ID: 0x14600000 / 4
          Current Available (mA): 500
          Current Required (mA): 200
          Extra Operating Current (mA): 0
...(略)

上2行に書かれてあるProduct IDVendor IDがそれぞれ先程のrequestDeviceメソッドに与えるfiltersの中の要素になります。

プロダクトIDは変わる事があるのでベンダIDのみfiltersに指定することも可能です。その場合であれば接続されてる機器情報を調べずとも、filtersのvendorIdに0x054cと与えましょう。


上記2つのファイルを用意し、ローカルでホストします。

ホストしない(file://でアクセスしようとする)とUSBデバイスと接続できません。

ローカルでホストする方法はいくつかありますが、単純にコマンド一発実行するだけで手軽にできるのでPythonのhttpモジュールを用います。上記2ファイルを設置したディレクトリで下記コマンドを実行します。

$ python3 -m http.server 8000

立ち上がったら、ブラウザでhttp://localhost:8000にアクセスします。

スクリーンショット 2020-01-03 18.39.14.png

RC-S380/Sを選択し、接続をクリックするとブラウザとPaSoRiで通信を始めるので、NFCタグを読ませてみます。

下記画像のように、タグの中身が表示されていれば成功です。

スクリーンショット 2020-01-03 18.40.55.png


この方法はType2Tagを読む方法です。この上にNFC/FeliCa対応のスマホを載せてみたり、Suicaなどを載せてみると値が一意に定まらず、いろんな結果が表示されます。

FeliCaの一意なIDmを読みたい場合には、上述しましたが、当記事を書く際に参考にさせていただいたこちらの記事でお試しください。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2