Python2 + nfcpyで、W525DZのFeliCa Plugを読んでみた

毎年冬になるとつらいのが冷え性です。

今までは身体が冷えてるような気がするから冷え性だろうと思っていました。しかし、「推測するな計測せよ」という言葉を思い出しました。

そこで記録が手軽に残せる体温計を探してみました。すると、NFCでデータを転送し、専用アプリでデータを管理できる体温計がありました。
WOMAN℃ テルモ女性体温計W525DZ|女性体温計 |テルモ 一般のお客様向け情報

目的が違うような気もしますが、データを残しておきたい気持ちが勝ったため、入手しました。

 
残念なのは専用アプリがWindows向けであり、Macでは使えないことです。

もしかしたら nfcpy を使えばデータを読めるのではないかと思い、試してみることにしました。
nfcpy/nfcpy: A Python module to read/write NFC tags or communicate with another NFC device.

 
結論からすると、Macでもデータは読み込めたものの、データのフォーマットが公開されていない、もしくは、暗号化された領域にデータを保存しているため、うまくいきませんでした。

とはいえ、せっかくなので、読み込んだデータなどをメモしておきます。

 
目次

 

環境

なお、nfcpyはPython3対応を進めているようです。進捗状況は以下のissueにありました。
Support Python 3 · Issue #47 · nfcpy/nfcpy

 

実装内容

NFCタグとの対話をハンドリングする

NFCタグとの対話をハンドリングする方法は、以下にありました。
Read and write tags | Getting started — nfcpy 0.13.4 documentation

ContactlessFrontend は使い終わったら close() する必要がありますが、Pythonのwith文に対応しているため、以下のように書けます。

with nfc.ContactlessFrontend('usb') as clf:
    clf.connect(rdwr={'on-connect': connected})

 
あとは、 connected() コールバック関数を定義し、その中にNFCタグとの通信を記載すれば良さそうです。

コールバック関数には引数tagが渡されてくるため、その中身を見てみました。IDやPMMはボカしていますが、こんな感じでした。  

def connected(tag):
    print tag
    # => Type3Tag 'FeliCa Plug (RC-S926)' ID=03xxxxxxxxxxxxxx PMM=01xxxxxxxxxxxxxx SYS=FEE1

    print type(tag)
    # => <class 'nfc.tag.tt3_sony.FelicaPlug'>

    print dir(tag)
    # =>
    # ['IC_CODE_MAP', 'NDEF', 'TYPE', '__class__', '__delattr__', '__dict__', '__doc__',
    #  '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__',
    #  '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
    #  '__subclasshook__', '__weakref__', '_authenticated', '_clf', '_format', '_is_present',
    #  '_ndef', '_nfcid', '_product', '_target', 'authenticate', 'clf', 'dump', 'dump_service',
    #  'format', 'identifier', 'idm', 'is_authenticated', 'is_present', 'ndef', 'pmm', 'polling',
    #  'product', 'protect', 'read_from_ndef_service', 'read_without_encryption',
    #  'send_cmd_recv_rsp', 'sys', 'target', 'type', 'write_to_ndef_service',
    #  'write_without_encryption']

 
この結果より、

  • システムコードが FEE1
  • 引数tagが FelicaPlug

であることから、普通のFeliCaではなく、W525DZではFeliCa Plug を使っているようでした。

 

Polling

続いて、pollingを試してみます。

nfcpyの関数 polling() の引数 system_code にはシステムコードが必要そうです。

システムコードは tag.sys に設定されていることから、それを使います。

def connected(tag):
    print tag.polling(tag.sys)
    # => (bytearray(b'\x03\xxx\xxx\xxx\xxx\xxx\xxx\xxx'),
    #     bytearray(b'\x01\xxx\xxx\xxx\xxx\xxx\xxx\xxx'))

先ほど tagをprintしたときに出てきた値を、bytearrayのタプルとして取得できました。

 

Read Without Encryption

さらに、暗号化されていない部分からデータを読み込んでみます。

今回は、tagオブジェクトの read_without_encryption() を使えば良さそうでした。

引数は2つあり、サービスコードのリストとブロックコードのリストでした。

サービスコードは nfc.tag.tt3.ServiceCode クラスのインスタンスを渡せば良さそうでした。

必要な値は、FeliCa Plug ユーザーズマニュアル v1.14の

  • p47 より、サービスコードリスト順番は 0
  • p56 より、サービスコードは 000Bh
  • p55 より、サービス数は 1

と推定したため、

sc = nfc.tag.tt3.ServiceCode(0, 0x0b)

の1要素を持つリストとしました(とはいえ、第一引数 number がサービスコードリスト順番なのかは自信がないですが...)。

 
ブロックコードについては nfc.tag.tt3.BlockCode クラスのインスタンスを使います。

コンストラクタの引数については、

  • number : ブロック番号
  • service : サービスコードリストのindex (今回は1つしかないので 0 )

を指定したのを2つ用意しました。

bc1 = nfc.tag.tt3.BlockCode(0, service=0)
bc2 = nfc.tag.tt3.BlockCode(1, service=0)

 
あとはそれらを read_without_encryption() メソッドに渡し、取得したデータを binascii.hexlify() で16進数表記したのをprintしてみました。

data = tag.read_without_encryption([sc], [bc1, bc2])
print '{}'.format(binascii.hexlify(data))
# => 02fd8c73947acef9742874c2b8429cc1d7dab10accf72bd5318863f862dc0371

 
何らかの値は取れているようですが、どの位置の数字が何を意味しているのか分からないため、解読できませんでした。

 

dump()

他にメソッドがないかを探したところ、 dump() があったため試してみました。

print tag.dump()
# => ['This is not an NFC Forum Tag.']

残念ながら、FeliCa Plugでは使えないようです。

 

dump_service()

他にもダンプできそうなメソッドとして dump_service() があったため、試してみました。

引数にはサービスコードが必要そうでしたが、先ほど作成したサービスコードを流用しました。

sc = nfc.tag.tt3.ServiceCode(0, 0x0b)

print tag.dump_service(sc)
# =>
# ['0000: 02 fd 8c 73 94 7a ce f9 74 28 74 c2 b8 42 9c c1 |...s.z..t(t..B..|',
#  '*     02 fd 8c 73 94 7a ce f9 74 28 74 c2 b8 42 9c c1 |...s.z..t(t..B..|',
#  '6962: 02 fd 8c 73 94 7a ce f9 74 28 74 c2 b8 42 9c c1 |...s.z..t(t..B..|']

dump_service() はサービスに対応する全データブロックをダンプするとのことで、実行が終わるまで少々待ちました。

データは出力されたものの、こちらもフォーマットが不明なため、解読できませんでした。

 
ここまでで、フォーマットが分からないことには解読できなさそうと考え、これ以上の追求はやめました。

 

参考

 
また、ユーザーズマニュアル中の数値については、ユーザーズマニュアル(v1.14)のp3に記載がありました。

ソースコード

GitHubに上げました。 felica_plug/read_w525.py が今回のソースコードです。
https://github.com/thinkAmi-sandbox/nfcpy-sample