Help us understand the problem. What is going on with this article?

Suica (含むPASMO等)をMacで読みたい

More than 5 years have passed since last update.

はじめに

2007年にMacにPaSoRiを接続してSuicaやPASMOの履歴情報を読み出すプログラムを書いて公開しましたが、その後ほったらかしていました。
Sony さんが非接触ICカードリーダー/ライター PaSoRi®(パソリ) 「RC-S390」を発売し、iPhone/iPad 等で利用ができるようになっていいのですが、折角のWi-Fi 対応の PaSoRi なので、Macでも利用できればと思って心待ちにしているものの、未だにSonyさんからアプリの提供はありません。
古い情報ですが、Suica(含PASMO等)のデータ構造等を開発者の皆さんに公開して、Mac版を作ってもらいたいという他力本願の投稿です。

Suicaのデータ構造

この情報は、自分の Suica のデータをダンプしてリバースした結果ですので、私の推測であって、正しいという保証はありません。たまたまうまく行っているのかもしれないということで責任は負えません。

Suicaのデータは残金や通番など一部のバイトの並びが逆(リトルエンディアン)です。

  1. 最初の1バイト: データ種別

    • 0x1B だとクレジット入金
    • 0x07 or 0x08だと入金
    • 0x46だとサンクスチャージの入金
    • 0x16が自動改札乗降
    • 0xC7が購買のようです
  2. 5バイト目から2バイト:日付

    • 先頭から7ビットが年
    • 4ビットが月
    • 残り5ビットが日のようです。  (ここが面倒でした。パズルを解くみたいで楽しかったけど。)
  3. 7バイト目からの2バイト:入った駅(コード)

  4. 続く2バイト:出た駅(コード)

    駅のコードは、路線コード/駅コードの組合わせで、有志によるデータベースが公開されています。IC SFCard Fan DB Srevice このソフトではコードからの変換はしていません。

  5. 12バイト目から2バイト:残金(リトルエンディアン)

MacでSuicaを読むプログラム

2007年に公開したプログラムです。
殆どの機能はネット上に公開されているlibpasoril]に依存していますが現時点では公開されていません。(残念です)
更にlibpasoriはlibusbを利用しています。(Wi-Fi利用なら別のライブラリーが必要でしょう)

ソースはここに再掲しますが、詳細は Macで Suica や PASMO の履歴を読む
を御覧ください。

SuicaValue.c
#include <stdio.h>
#include <stdlib.h>
#include "libpasori.h"

int
main(void){
    pasori* p;
    felica* f;
    int i;
    int m;
    int wk, wk2;
    uint8 b[16];

    p = pasori_open(NULL);
    if(!p){
        printf("error\n");
        exit(-1);
    }
    pasori_init(p);
    f = felica_polling(p,0x0003,0,0);
    printf("*** Suica Value ***\n");
    i=0;
        while(!felica_read_without_encryption02(f,0x090f,0,i,b)){
            printf("[%02d] : ",i);
            printf("%02X ",b[0]);
            printf("%02X ",b[1]);
            printf("%02X",b[2]);
            printf("%02X ",b[3]);
            wk=b[4]>>1;
            wk=wk&0x7F;
            printf("%02d/",wk);
            wk=b[4]&0x01;
            wk=wk<<3;
            wk2=b[5]>>5;
            wk2=wk2&0x07;
            wk=wk+wk2;
            printf("%02d/",wk);
            wk=b[5]&0x1F;
            printf("%02d ",wk);
            printf("%02X-%02X -> ",b[6],b[7]);
            printf("%02X-%02X      ",b[8],b[9]);
            printf("  % 6d Yen        ",b[11]*256+b[10]);
            printf("%02X%02X%02X%02X\n",b[12],b[13],b[14],b[15]);
        i++;
    }
    pasori_close(p);
    return 0;
}

更に、これを利用して見やすくフォーマットするAppleScriptのソースも再掲しておきます。

SuicaValue.scpt
-- 「SuicaValue.scpt」
-- PaSoRiでSuicaの履歴を抽出して表示するスクリプトです。
-- Suicaを読むアプリケーション「SuicaValue」はホームディレクトリ直下に置いて実行権を与えておいて下さい。
-- 最初に駅名の検索/表示をするかを問い合わせます。駅名の検索/表示ではインターネットに接続して検索しますので、
-- ネットに接続されていることが必要です。(商品の購入では表示されません)
-- 結果を表示後にOKボタンを押すと、表示結果のテキストをクリップボードにコピーして終了します。
-- このスクリプトでは、IC SFCardFanのページ(http://www.denno.net/SFCardFan/)にあるSOAPのサービスを
-- 利用して駅名と店名を検索しています。(サービスの提供に感謝します)

property SOAP_Endpoint_URL : "http://www.denno.net/SFCardFan/soapserver.php"
property SOAP_app : "soap"
property method_name_ST : "getStationName"
property method_name_SH : "getShopName"
property method_namespace_URI : ""
property SOAP_action : ""

display dialog " 駅名/店名検索をしますか?" & return & "(インターネットに接続します)" buttons {"Yes", "No"} default button 2
set SerchOn to button returned of result

-- SuicaValue のある場所を指定
set cmdPath to "$HOME/SuicaValue"
do shell script cmdPath
set SuicaValue to result as text

set dataCount to the number of paragraphs of SuicaValue
set sortedData to ""
repeat with i from 0 to dataCount - 2
    set eachData to paragraph (dataCount - i) of SuicaValue
    set Proc to word 2 of eachData
    if Proc = "00" then
    else
        set lineData to characters 19 thru 26 of eachData & " "
        set zankin to (characters 43 thru 55 of eachData) as text
        if Proc = "1B" or Proc = "07" or Proc = "08" or Proc = "46" then
            set lineData to (lineData & " 入金後 " & word 1 of zankin as string) & "円"
            set lineData to lineData & " 場所:" & (characters 28 thru 32 of eachData)
        else if Proc = "C7" or Proc = "C8" then
            set lineData to (lineData & " 購入後 " & word 1 of zankin as string) & "円"
            set lineData to lineData & " 店舗:" & (characters 28 thru 32 of eachData)
            set lineData to lineData & "ー" & (characters 37 thru 41 of eachData)
            if SerchOn = "Yes" then
                set shop to my shopname(Proc as string, (characters 37 thru 41 of eachData) as string)
                set lineData to lineData & " " & shop
            end if
        else
            set lineData to lineData & " 乗車後 " & word 1 of zankin & "円"
            set lineData to lineData & " 区間:" & (characters 28 thru 32 of eachData)
            set lineData to lineData & "~" & (characters 37 thru 41 of eachData)
            -- 駅名の検索
            if SerchOn = "Yes" then
                set fromStation to my stationname((characters 28 thru 32 of eachData) as string)
                set toStation to my stationname((characters 37 thru 41 of eachData) as string)
                set lineData to lineData & " " & fromStation & " --> " & toStation
            end if
        end if
        set sortedData to sortedData & lineData & (ASCII character 13)
    end if
end repeat
display dialog sortedData
set the clipboard to sortedData as string

-- 処理終了(結果をクリップボードにコピー)

-- 駅名の検索
on stationname(LSCode)
    set LC to Hex2Dec((characters 1 thru 2 of LSCode) as string)
    set SC to Hex2Dec((characters 4 thru 5 of LSCode) as string)
    if LC < 128 then
        set AC to "0"
    else
        set AC to "1"
    end if
    set the method_parameters to {AreaCode:AC, LineCode:LC, StationCode:SC}
    copy my SOAP_call_ST(SOAP_Endpoint_URL, method_name_ST, method_namespace_URI, method_parameters, SOAP_action) to {call_indicator, call_result}
    if the call_indicator is false then
        beep
        display dialog "An error occurred." & return & return & call_result buttons {"Cancel"} default button 1
    else
        try
            set findName to value of item 6 of item 1 of every item of call_result
        on error
            set findName to "駅名不明"
        end try
        return findName
    end if
end stationname

-- 店舗名の検索
on shopname(TermCode, LSCode)
    set TC to Hex2Dec(TermCode)
    set LC to Hex2Dec((characters 1 thru 2 of LSCode) as string)
    set SC to Hex2Dec((characters 4 thru 5 of LSCode) as string)
    set AC to "1"
    set the method_parameters to {AreaCode:AC, TerminalCode:TC, LineCode:LC, StationCode:SC}
    copy my SOAP_call_SH(SOAP_Endpoint_URL, method_name_SH, method_namespace_URI, method_parameters, SOAP_action) to {call_indicator, call_result}
    if the call_indicator is false then
        beep
        display dialog "An error occurred." & return & return & call_result buttons {"Cancel"} default button 1
    else
        try
            set findName to value of item 5 of item 1 of every item of call_result
            set findName to findName & "、" & value of item 6 of item 1 of every item of call_result
        on error
            set findName to "店名不明"
        end try
        return findName
    end if
end shopname

-- SOAP 検索(駅名)
on SOAP_call_ST(SOAP_Endpoint_URL, method_name_ST, method_namespace_URI, method_parameters, SOAP_action)
    try
        using terms from application "http://www.apple.com/"
            tell application SOAP_Endpoint_URL
                set this_result to call soap {method name:method_name_ST, method namespace uri:method_namespace_URI, parameters:method_parameters, SOAPAction:SOAP_action}
            end tell
        end using terms from
        return {true, this_result}
    on error error_message number error_number
        if the error_number is -916 then set the error_message to "インターネットに接続できませんでした。"
        return {false, error_message}
    end try
end SOAP_call_ST

-- SOAP 検索(店舗)
on SOAP_call_SH(SOAP_Endpoint_URL, method_name_SH, method_namespace_URI, method_parameters, SOAP_action)
    try
        using terms from application "http://www.apple.com/"
            tell application SOAP_Endpoint_URL
                set this_result to call soap {method name:method_name_SH, method namespace uri:method_namespace_URI, parameters:method_parameters, SOAPAction:SOAP_action}
            end tell
        end using terms from
        return {true, this_result}
    on error error_message number error_number
        if the error_number is -916 then set the error_message to "インターネットに接続できませんでした。"
        return {false, error_message}
    end try
end SOAP_call_SH

-- 16進を10進に変換(2桁のみ)
on Hex2Dec(HEX)
    set ans to ((offset of (character 1 of HEX) in "0123456789ABCDEF") - 1) * 16
    set ans to ans + (offset of (character 2 of HEX) in "0123456789ABCDEF") - 1
    return ans
end Hex2Dec
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした