| 1 |
/* |
| 2 |
* FeliCa2Money |
| 3 |
* |
| 4 |
* Copyright (C) 2001-2008 Takuya Murakami |
| 5 |
* |
| 6 |
* This program is free software; you can redistribute it and/or modify |
| 7 |
* it under the terms of the GNU General Public License as published by |
| 8 |
* the Free Software Foundation; either version 2 of the License, or |
| 9 |
* (at your option) any later version. |
| 10 |
* |
| 11 |
* This program is distributed in the hope that it will be useful, |
| 12 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 |
* GNU General Public License for more details. |
| 15 |
* |
| 16 |
* You should have received a copy of the GNU General Public License |
| 17 |
* along with this program; if not, write to the Free Software |
| 18 |
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 19 |
*/ |
| 20 |
|
| 21 |
// Suica/PASMO/ICOCA/PiTaPa/Toica など交通系カード処理 |
| 22 |
|
| 23 |
using System; |
| 24 |
using System.Collections.Generic; |
| 25 |
using System.Text; |
| 26 |
using FelicaLib; |
| 27 |
|
| 28 |
namespace FeliCa2Money |
| 29 |
{ |
| 30 |
class Suica : CardWithFelicaLib |
| 31 |
{ |
| 32 |
// 物販エリアコード |
| 33 |
public const int AreaSuica = 1; |
| 34 |
public const int AreaIcoca = 2; |
| 35 |
public const int AreaIruca = 4; |
| 36 |
|
| 37 |
private StationCode stCode; |
| 38 |
|
| 39 |
public Suica() |
| 40 |
{ |
| 41 |
org = "Suica"; |
| 42 |
cardName = "Suica"; |
| 43 |
|
| 44 |
systemCode = (int)SystemCode.Suica; |
| 45 |
serviceCode = 0x090f; // 履歴エリアのサービスコード |
| 46 |
needReverse = true; |
| 47 |
|
| 48 |
stCode = new StationCode(); |
| 49 |
} |
| 50 |
|
| 51 |
public override void Dispose() |
| 52 |
{ |
| 53 |
base.Dispose(); |
| 54 |
stCode.Dispose(); |
| 55 |
} |
| 56 |
|
| 57 |
// カード ID を取得 |
| 58 |
// Suica では IDm を用いる |
| 59 |
public override void analyzeCardId(Felica f) |
| 60 |
{ |
| 61 |
byte[] data = f.IDm(); |
| 62 |
if (data == null) |
| 63 |
{ |
| 64 |
throw new Exception("IDm を読み取れません"); |
| 65 |
} |
| 66 |
|
| 67 |
accountId = ""; |
| 68 |
for (int i = 0; i < 8; i++) { |
| 69 |
accountId += data[i].ToString("X2"); |
| 70 |
} |
| 71 |
} |
| 72 |
|
| 73 |
// 後処理 |
| 74 |
// Suica の場合、履歴には残高しか記録されていない。 |
| 75 |
// ここでは、残高の差額から各トランザクションの金額を計算する |
| 76 |
// (このため、最も古いエントリは金額を計算できない) |
| 77 |
protected override void PostProcess(List<Transaction> list) |
| 78 |
{ |
| 79 |
int prevBalance = 0; |
| 80 |
|
| 81 |
foreach (Transaction t in list) |
| 82 |
{ |
| 83 |
t.value = t.balance - prevBalance; |
| 84 |
prevBalance = t.balance; |
| 85 |
} |
| 86 |
list.RemoveAt(0); // 最古のエントリは捨てる |
| 87 |
} |
| 88 |
|
| 89 |
// トランザクション解析 |
| 90 |
public override bool analyzeTransaction(Transaction t, byte[] data) |
| 91 |
{ |
| 92 |
int ctype = data[0]; // 端末種 |
| 93 |
int proc = data[1]; // 処理 |
| 94 |
int date = read2b(data, 4); // 日付 |
| 95 |
int balance = read2l(data, 10); // 残高(little endian) |
| 96 |
int seq = read3b(data, 12); // 連番 |
| 97 |
int region = data[15]; // リージョン |
| 98 |
|
| 99 |
// 処理 |
| 100 |
t.desc = procType(proc); |
| 101 |
|
| 102 |
// 残高 |
| 103 |
t.balance = balance; |
| 104 |
|
| 105 |
// 金額は PostProcess で計算する |
| 106 |
t.value = 0; |
| 107 |
|
| 108 |
// 日付 |
| 109 |
int yy = (date >> 9) + 2000; |
| 110 |
int mm = (date >> 5) & 0xf; |
| 111 |
int dd = date & 0x1f; |
| 112 |
try |
| 113 |
{ |
| 114 |
t.date = new DateTime(yy, mm, dd, 0, 0, 0); |
| 115 |
} |
| 116 |
catch |
| 117 |
{ |
| 118 |
// 日付異常(おそらく空エントリ) |
| 119 |
return false; |
| 120 |
} |
| 121 |
|
| 122 |
// ID |
| 123 |
t.id = seq; |
| 124 |
|
| 125 |
// 駅名/店舗名などを調べる |
| 126 |
int in_line = -1; |
| 127 |
int in_sta = -1; |
| 128 |
int out_line, out_sta; |
| 129 |
string[] in_name = null, out_name = null; |
| 130 |
int area; |
| 131 |
|
| 132 |
switch (ctype) |
| 133 |
{ |
| 134 |
case CT_SHOP: |
| 135 |
case CT_VEND: |
| 136 |
// 物販/自販機 |
| 137 |
area = Properties.Settings.Default.ShopAreaPriority; |
| 138 |
|
| 139 |
//time = (data[6] << 8) | data[7]; |
| 140 |
out_line = data[8]; |
| 141 |
out_sta = data[9]; |
| 142 |
|
| 143 |
// 優先エリアで検索 |
| 144 |
out_name = stCode.getShopName(area, ctype, out_line, out_sta); |
| 145 |
if (out_name == null) |
| 146 |
{ |
| 147 |
// 全エリアで検索 |
| 148 |
out_name = stCode.getShopName(-1, ctype, out_line, out_sta); |
| 149 |
} |
| 150 |
break; |
| 151 |
|
| 152 |
case CT_CAR: |
| 153 |
// 車載端末(バス) |
| 154 |
out_line = read2b(data, 6); |
| 155 |
out_sta = read2b(data, 8); |
| 156 |
out_name = stCode.getBusName(out_line, out_sta); |
| 157 |
break; |
| 158 |
|
| 159 |
default: |
| 160 |
// それ以外(運賃、チャージなど) |
| 161 |
in_line = data[6]; |
| 162 |
in_sta = data[7]; |
| 163 |
out_line = data[8]; |
| 164 |
out_sta = data[9]; |
| 165 |
if (in_line == 0 && in_sta == 0 && out_line == 0 && out_sta == 0) |
| 166 |
{ |
| 167 |
break; |
| 168 |
} |
| 169 |
|
| 170 |
// エリアを求める |
| 171 |
if (region >= 1) { |
| 172 |
area = 2; // 関西公営・私鉄 |
| 173 |
} |
| 174 |
else if (in_line >= 0x80) |
| 175 |
{ |
| 176 |
area = 1; // 関東公営・私鉄 |
| 177 |
} |
| 178 |
else |
| 179 |
{ |
| 180 |
area = 0; // JR |
| 181 |
} |
| 182 |
|
| 183 |
in_name = stCode.getStationName(area, in_line, in_sta); |
| 184 |
out_name = stCode.getStationName(area, out_line, out_sta); |
| 185 |
break; |
| 186 |
} |
| 187 |
|
| 188 |
// 備考の先頭には端末種を入れる |
| 189 |
t.memo = consoleType(ctype); |
| 190 |
|
| 191 |
switch (ctype) |
| 192 |
{ |
| 193 |
case CT_SHOP: |
| 194 |
case CT_VEND: |
| 195 |
if (out_name != null) |
| 196 |
{ |
| 197 |
// 適用に店舗名を入れる |
| 198 |
t.desc += " " + out_name[0] + " " + out_name[1]; |
| 199 |
} |
| 200 |
else |
| 201 |
{ |
| 202 |
// 店舗名が不明の場合、適用には出線区/出駅順コードをそのまま付与する。 |
| 203 |
// こうしないと Money が過去の履歴から誤って店舗名を補完してしまい |
| 204 |
// 都合がわるいため |
| 205 |
t.desc += " 店舗コード:" + out_line.ToString("X02") + out_sta.ToString("X02"); |
| 206 |
} |
| 207 |
break; |
| 208 |
|
| 209 |
case CT_CAR: |
| 210 |
if (out_name != null) |
| 211 |
{ |
| 212 |
// 適用にバス会社名、備考に停留所名を入れる |
| 213 |
t.desc += " " + out_name[0]; |
| 214 |
t.memo += " " + out_name[1]; |
| 215 |
} |
| 216 |
break; |
| 217 |
|
| 218 |
default: |
| 219 |
if (in_line == 0 && in_sta == 0 & out_line == 0 && out_sta == 0) |
| 220 |
{ |
| 221 |
// チャージなどの場合は、何も追加しない |
| 222 |
break; |
| 223 |
} |
| 224 |
|
| 225 |
// 適用に入会社または出会社を追加 |
| 226 |
if (in_name != null) |
| 227 |
{ |
| 228 |
t.desc += " " + in_name[0]; |
| 229 |
} |
| 230 |
else if (out_name != null) |
| 231 |
{ |
| 232 |
t.desc += " " + out_name[0]; |
| 233 |
} |
| 234 |
|
| 235 |
// 備考に入出会社/駅名を記載 |
| 236 |
if (in_name != null) { |
| 237 |
t.memo += " " + in_name[0] + "(" + in_name[1] + ")"; |
| 238 |
} else { |
| 239 |
t.memo += " 未登録"; |
| 240 |
} |
| 241 |
t.memo += " - "; |
| 242 |
|
| 243 |
if (out_name != null) { |
| 244 |
t.memo += out_name[0] + "(" + out_name[1] + ")"; |
| 245 |
} else { |
| 246 |
t.memo += "未登録"; |
| 247 |
} |
| 248 |
break; |
| 249 |
} |
| 250 |
|
| 251 |
return true; |
| 252 |
} |
| 253 |
|
| 254 |
private const int CT_SHOP = 199; // 物販端末 |
| 255 |
private const int CT_VEND = 200; // 自販機 |
| 256 |
private const int CT_CAR = 5; // 車載端末(バス) |
| 257 |
|
| 258 |
// 端末種文字列を返す |
| 259 |
private string consoleType(int ctype) |
| 260 |
{ |
| 261 |
switch (ctype) { |
| 262 |
case CT_SHOP: return "物販端末"; |
| 263 |
case CT_VEND: return "自販機"; |
| 264 |
case CT_CAR: return "車載端末"; |
| 265 |
case 3: return "清算機"; |
| 266 |
case 4: return "携帯型端末"; |
| 267 |
case 8: return "券売機"; |
| 268 |
case 9: return "入金機"; |
| 269 |
case 18: return "券売機"; |
| 270 |
case 20: |
| 271 |
case 21: return "券売機等"; |
| 272 |
case 22: return "改札機"; |
| 273 |
case 23: return "簡易改札機"; |
| 274 |
case 24: |
| 275 |
case 25: return "窓口端末"; |
| 276 |
case 26: return "改札端末"; |
| 277 |
case 27: return "携帯電話"; |
| 278 |
case 28: return "乗継精算機"; |
| 279 |
case 29: return "連絡改札機"; |
| 280 |
case 31: return "簡易入金機"; |
| 281 |
case 70: |
| 282 |
case 72: return "VIEW ALTTE"; |
| 283 |
} |
| 284 |
return "不明"; |
| 285 |
} |
| 286 |
|
| 287 |
// 処理種別文字列を返す |
| 288 |
private string procType(int proc) |
| 289 |
{ |
| 290 |
switch (proc) { |
| 291 |
case 1: return "運賃"; |
| 292 |
case 2: return "チャージ"; |
| 293 |
case 3: return "券購"; |
| 294 |
case 4: |
| 295 |
case 5: return "清算"; |
| 296 |
case 6: return "窓出"; |
| 297 |
case 7: return "新規"; |
| 298 |
case 8: return "控除"; |
| 299 |
case 13: return "バス"; //PiTaPa系 |
| 300 |
case 15: return "バス"; //IruCa系 |
| 301 |
case 17: return "再発行"; |
| 302 |
case 19: return "支払"; |
| 303 |
case 20: |
| 304 |
case 21: return "オートチャージ"; |
| 305 |
case 31: return "バスチャージ"; |
| 306 |
case 35: return "券購"; |
| 307 |
case 70: return "物販"; |
| 308 |
case 72: return "特典"; |
| 309 |
case 73: return "入金"; |
| 310 |
case 74: return "物販取消"; |
| 311 |
case 75: return "入場物販"; |
| 312 |
case 132: return "精算(他社)"; |
| 313 |
case 133: return "精算(他社入場)"; |
| 314 |
case 198: return "物販(現金併用)"; |
| 315 |
case 203: return "物販(入場現金併用)"; |
| 316 |
} |
| 317 |
return "不明"; |
| 318 |
} |
| 319 |
} |
| 320 |
} |