Ubuntu + Pasori RC-S380 で Suica を読み込むときのトラブルシュートメモ

# これはなに？

Ubuntu 22.04、SONY Pasori RC-S380、nfcpy を使用して Suica を読み込もうとしたときのトラブルシュートメモです。

# いきなりソースコード

とりあえず、こんなコードを動かしてみました。よくあるやつなので、スルーしていただいて大丈夫です。

実行には nfcpy が必要です。Poetry等を使用して環境を作るか、あるいは pip を使用するかして、nfcpy をインストールしてください。


```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import itertools
import nfc
from struct import unpack
from datetime import date, time, datetime
import csv

reader_id = 'usb:054c:06c1'
service_code_purchase = 0x090f
service_code_history = 0x108f
eki_code = {}

# 駅コード読み込み
with open("ekicode.csv") as csvfile:
    reader = csv.DictReader(csvfile, fieldnames=[
                            'area', 'line', 'station', 'company_name', 'line_name', 'station_name'])
    for row in reader:
        area = int(row['area'], 16)
        line = int(row['line'], 16)
        station = int(row['station'], 16)

        if not area in eki_code:
            eki_code[area] = {}
        if not line in eki_code[area]:
            eki_code[area][line] = {}

        eki_code[area][line][station] = row['station_name']


def resolve_station_name(area_code, line_code, station_code):
    try:
        return eki_code[area_code][line_code][station_code]
    except KeyError:
        return '不明'


def read_blocks(tag, sc):
    for i in itertools.count():
        try:
            # 例外起きるまで読み出し
            bc = nfc.tag.tt3.BlockCode(i, service=0)
            yield tag.read_without_encryption([sc], [bc])
        except nfc.tag.tt3.Type3TagCommandError:
            break


def read_purchase(tag):
    """
    支払い履歴
    """
    print("### Reading purchase history.")

    sc = nfc.tag.tt3.ServiceCode(
        service_code_purchase >> 6, service_code_purchase & 0x1f)

    for data in read_blocks(tag, sc):
        # print("" . join(['%02x ' % s for s in data]))

        # 0 B 機種種別
        # 1 B 利用種別
        # 2 B 支払い種別
        # 3 B 入出場種別
        # 4 H 出場日付
        # 5 B 入場路線コード
        # 6 B 入場駅順コード
        # 7 B 出場路線コード
        # 8 B 出場駅順コード
        # 9 H 残額
        # 10 B 不明
        # 11 H 取引連番
        # 12 地域コード等
        be = unpack('>BBBBHBBBBHBHB', data)
        le = unpack('<BBBBHBBBBHBHB', data)

        # 鉄道のみ対応
        if not be[1] in {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x14}:
            next

        p_date = date((be[4] >> 9) + 2000, (be[4] >> 5) & 0xf, be[4] & 0x1f)
        p_dep_area_code = (be[12] >> 6) & 0x3
        p_arr_area_code = (be[12] >> 4) & 0x3

        dep_station_name = resolve_station_name(p_dep_area_code, be[5], be[6])
        arr_station_name = resolve_station_name(p_arr_area_code, be[7], be[8])

        print(p_date.strftime('%Y-%m-%d'),
              f'{dep_station_name} -> {arr_station_name} {le[9]}円',
              f'{p_dep_area_code} {be[5]} {be[6]} -> {p_dep_area_code} {be[7]} {be[8]}')


def read_history(tag):
    """
    改札履歴
    """
    print("### Reading gate history.")

    sc = nfc.tag.tt3.ServiceCode(
        service_code_history >> 6, service_code_history & 0x1f)

    for data in read_blocks(tag, sc):
        # print("" . join(['%02x ' % s for s in data]))

        be = unpack('>BBBBHHBBHHBB', data)
        h_line_code = be[2]
        h_station_code = be[3]
        h_time = datetime((be[5] >> 9) + 2000, (be[5] >> 5) & 0xf,
                          be[5] & 0x1f, int(hex(be[6])[2:]), int(hex(be[7])[2:]), 0)

        print(h_time.strftime('%Y-%m-%dT%H:%M:%S'), h_station_code)


def connected(tag):
    print(tag)

    if not isinstance(tag, nfc.tag.tt3.Type3Tag):
        print("error: tag isn't Type3Tag")
        return

    read_purchase(tag)

    read_history(tag)


# 接続開始
clf = nfc.ContactlessFrontend(reader_id)
try:
    clf.connect(rdwr={'on-connect': connected})
finally:
    clf.close()

```

Pasori に Suica をかざすと、なにか出力されるはずです。



# トラブルシュート

ここからが本題です。


## NO SUCH DEVICE と言われる

プログラムの中で定義している`reader_id` __usb:054c:06c1__ が間違っているのかもしれません。

`lsusb` コマンドで確認してください。

下記は、自分の環境の場合です。

```console
% lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 046d:c52b Logitech, Inc. Unifying Receiver
Bus 001 Device 002: ID 17ef:6009 Lenovo ThinkPad Keyboard with TrackPoint
Bus 001 Device 007: ID 054c:06c1 Sony Corp. RC-S380/S
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
```

## パーミッションがなさそう？

自分自身を `plugdev` グループへ追加し、ログインし直してください。

```bash
gpasswd -a `whoami` plugdev
```

Pasori を刺して、`/dev/bus/usb` の下にあるデバイスを確認してみてください。`plugdev` グループに読み書き権限があればOKです。

下記は、自分の環境の場合です。

```console
% ls -l /dev/bus/usb/001/007
crw-rw----+ 1 root plugdev 189, 6  3月 13 13:02 007
```

/dev/bus/usb 配下の 001/007 は、`lsusb` コマンドで確認できます。


## RESOURCE BUSY と言われる

デフォルトで読み込まれる `port100` モジュールと衝突しているのかもしれません。

いったん Pasori を抜き、以下の内容のファイルを `/etc/modprobe.d/blacklist-nfc.conf` として作成してください。

```
# Conflict with nfcpy
blacklist port100
```

作成したら、Pasori を刺してください。

以下のコマンドを実行し、モジュールがロードされていないことがわかればOKです。

```bash
lsmod | grep port100
```

## 駅名が見つからない

どういう理屈かわかりませんが、出場駅が 0 0 0 となっていることがあります。

探求中です。

# リンク集

https://github.com/furugori/ekicode

http://jennychan.web.fc2.com/format/suica.html