| 1 | #import "PasoriKit/pasori.h" |
|---|
| 2 | #import "PasoriKit/felica.h" |
|---|
| 3 | #import "PasoriKit/suica.h" |
|---|
| 4 | #import "PasoriKit/PasoriRequest.h" |
|---|
| 5 | |
|---|
| 6 | #import "MyAppController.h" |
|---|
| 7 | #import "SFCardDB.h" |
|---|
| 8 | |
|---|
| 9 | |
|---|
| 10 | @implementation MyAppController |
|---|
| 11 | |
|---|
| 12 | |
|---|
| 13 | - (id)init |
|---|
| 14 | { |
|---|
| 15 | self = [super init]; |
|---|
| 16 | if (self) { |
|---|
| 17 | suicaValues = [[NSMutableArray array] retain]; |
|---|
| 18 | } |
|---|
| 19 | return self; |
|---|
| 20 | } |
|---|
| 21 | |
|---|
| 22 | |
|---|
| 23 | - (void)dealloc |
|---|
| 24 | { |
|---|
| 25 | [suicaValues release]; |
|---|
| 26 | [usbPasori release]; |
|---|
| 27 | |
|---|
| 28 | // PaSoRi のノーティフィケーション通知を削除 |
|---|
| 29 | [usbNotify release]; |
|---|
| 30 | |
|---|
| 31 | [super dealloc]; |
|---|
| 32 | } |
|---|
| 33 | |
|---|
| 34 | |
|---|
| 35 | #pragma mark - |
|---|
| 36 | |
|---|
| 37 | |
|---|
| 38 | /// バイナリを16進数文字列に変換する |
|---|
| 39 | - (NSString *)bin2hex:(uint8_t *)data length:(int)len |
|---|
| 40 | { |
|---|
| 41 | NSMutableString * str = [NSMutableString stringWithString:@"0x"]; |
|---|
| 42 | int i; |
|---|
| 43 | |
|---|
| 44 | for (i = 0; i < len; i++) |
|---|
| 45 | { |
|---|
| 46 | [str appendFormat:@"%02X", data[i]]; |
|---|
| 47 | } |
|---|
| 48 | |
|---|
| 49 | return str; |
|---|
| 50 | } |
|---|
| 51 | |
|---|
| 52 | |
|---|
| 53 | /// Suica処理の説明文を取得 |
|---|
| 54 | - (NSString *)suicaDescription:(int)proc |
|---|
| 55 | { |
|---|
| 56 | NSString * note = nil; |
|---|
| 57 | |
|---|
| 58 | switch (proc) |
|---|
| 59 | { |
|---|
| 60 | case 1: |
|---|
| 61 | // 運賃支払(改札出場) |
|---|
| 62 | break; |
|---|
| 63 | |
|---|
| 64 | case 2: |
|---|
| 65 | note = @"チャージ"; |
|---|
| 66 | break; |
|---|
| 67 | |
|---|
| 68 | case 3: |
|---|
| 69 | note = @"券購(磁気券購入)"; |
|---|
| 70 | break; |
|---|
| 71 | |
|---|
| 72 | case 4: |
|---|
| 73 | note = @"精算"; |
|---|
| 74 | break; |
|---|
| 75 | |
|---|
| 76 | case 5: |
|---|
| 77 | note = @"精算 (入場精算)"; |
|---|
| 78 | break; |
|---|
| 79 | |
|---|
| 80 | case 6: |
|---|
| 81 | note = @"窓出 (改札窓口処理)"; |
|---|
| 82 | break; |
|---|
| 83 | |
|---|
| 84 | case 7: |
|---|
| 85 | note = @"新規 (新規発行)"; |
|---|
| 86 | break; |
|---|
| 87 | |
|---|
| 88 | case 8: |
|---|
| 89 | note = @"控除 (窓口控除)"; |
|---|
| 90 | break; |
|---|
| 91 | |
|---|
| 92 | case 13: |
|---|
| 93 | note = @"バス (PiTaPa系)"; |
|---|
| 94 | break; |
|---|
| 95 | |
|---|
| 96 | case 15: |
|---|
| 97 | note = @"バス (IruCa系)"; |
|---|
| 98 | break; |
|---|
| 99 | |
|---|
| 100 | case 17: |
|---|
| 101 | note = @"再発 (再発行処理)"; |
|---|
| 102 | break; |
|---|
| 103 | |
|---|
| 104 | case 19: |
|---|
| 105 | note = @"支払 (新幹線利用)"; |
|---|
| 106 | break; |
|---|
| 107 | |
|---|
| 108 | case 20: |
|---|
| 109 | note = @"入A (入場時オートチャージ)"; |
|---|
| 110 | break; |
|---|
| 111 | |
|---|
| 112 | case 21: |
|---|
| 113 | note = @"出A (出場時オートチャージ)"; |
|---|
| 114 | break; |
|---|
| 115 | |
|---|
| 116 | case 31: |
|---|
| 117 | note = @"入金 (バスチャージ)"; |
|---|
| 118 | break; |
|---|
| 119 | |
|---|
| 120 | case 35: |
|---|
| 121 | note = @"券購 (バス路面電車企画券購入)"; |
|---|
| 122 | break; |
|---|
| 123 | |
|---|
| 124 | case 70: |
|---|
| 125 | note = @"物販"; |
|---|
| 126 | break; |
|---|
| 127 | |
|---|
| 128 | case 72: |
|---|
| 129 | note = @"特典 (特典チャージ)"; |
|---|
| 130 | break; |
|---|
| 131 | |
|---|
| 132 | case 73: |
|---|
| 133 | note = @"入金 (レジ入金)"; |
|---|
| 134 | break; |
|---|
| 135 | |
|---|
| 136 | case 74: |
|---|
| 137 | note = @"物販取消"; |
|---|
| 138 | break; |
|---|
| 139 | |
|---|
| 140 | case 75: |
|---|
| 141 | note = @"入物 (入場物販)"; |
|---|
| 142 | break; |
|---|
| 143 | |
|---|
| 144 | case 198: |
|---|
| 145 | note = @"物現 (現金併用物販)"; |
|---|
| 146 | break; |
|---|
| 147 | |
|---|
| 148 | case 203: |
|---|
| 149 | note = @"入物 (入場現金併用物販)"; |
|---|
| 150 | break; |
|---|
| 151 | |
|---|
| 152 | case 132: |
|---|
| 153 | note = @"精算 (他社精算)"; |
|---|
| 154 | break; |
|---|
| 155 | |
|---|
| 156 | case 133: |
|---|
| 157 | note = @"精算 (他社入場精算)"; |
|---|
| 158 | break; |
|---|
| 159 | |
|---|
| 160 | default: |
|---|
| 161 | note = @"不明"; |
|---|
| 162 | break; |
|---|
| 163 | } |
|---|
| 164 | |
|---|
| 165 | return note; |
|---|
| 166 | } |
|---|
| 167 | |
|---|
| 168 | |
|---|
| 169 | /// Suica 利用履歴のアップデート |
|---|
| 170 | - (void)updateSuicaValue:(uint8_t *)binary index:(uint8_t)index |
|---|
| 171 | { |
|---|
| 172 | suica_value_t * value = (suica_value_t *)binary; |
|---|
| 173 | NSMutableDictionary * suicaValue = [NSMutableDictionary dictionary]; |
|---|
| 174 | |
|---|
| 175 | [suicaValue setObject:[NSData dataWithBytes:value length:sizeof(suica_value_t)] forKey:@"data"]; |
|---|
| 176 | |
|---|
| 177 | // 利用年月日 |
|---|
| 178 | NSCalendarDate * date = [NSCalendarDate dateWithYear:2000+suica_year(value) month:suica_month(value) day:suica_day(value) hour:0 minute:0 second:0 timeZone:nil]; |
|---|
| 179 | [suicaValue setObject:date forKey:@"date"]; |
|---|
| 180 | |
|---|
| 181 | // 残額 |
|---|
| 182 | [suicaValue setObject:[NSNumber numberWithUnsignedShort:suica_balacne(value)] forKey:@"balacne"]; |
|---|
| 183 | if (index == 0) |
|---|
| 184 | { // 先頭の残額を残高として表示 |
|---|
| 185 | [balacneView setIntValue:suica_balacne(value)]; |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | switch (value->proc) |
|---|
| 189 | { |
|---|
| 190 | case 1: |
|---|
| 191 | // 運賃支払(改札出場) |
|---|
| 192 | [suicaValue setObject:[self bin2hex:value->in_station length:sizeof(value->in_station)] forKey:@"in_station"]; |
|---|
| 193 | [suicaValue setObject:[self bin2hex:value->out_station length:sizeof(value->out_station)] forKey:@"out_station"]; |
|---|
| 194 | break; |
|---|
| 195 | } |
|---|
| 196 | |
|---|
| 197 | // 備考 |
|---|
| 198 | NSString * note = [self suicaDescription:value->proc]; |
|---|
| 199 | |
|---|
| 200 | if (note) |
|---|
| 201 | { |
|---|
| 202 | [suicaValue setObject:note forKey:@"note"]; |
|---|
| 203 | } |
|---|
| 204 | |
|---|
| 205 | // データの更新 |
|---|
| 206 | [suicaValues addObject:suicaValue]; |
|---|
| 207 | [suicaTableView noteNumberOfRowsChanged]; |
|---|
| 208 | } |
|---|
| 209 | |
|---|
| 210 | |
|---|
| 211 | /// Suicaデータをクリアする |
|---|
| 212 | - (void)suicaValueClean |
|---|
| 213 | { |
|---|
| 214 | [balacneView setStringValue:@""]; |
|---|
| 215 | |
|---|
| 216 | [suicaValues removeAllObjects]; |
|---|
| 217 | [suicaTableView reloadData]; |
|---|
| 218 | } |
|---|
| 219 | |
|---|
| 220 | |
|---|
| 221 | #pragma mark - |
|---|
| 222 | |
|---|
| 223 | |
|---|
| 224 | // アプリケーション起動完了通知 |
|---|
| 225 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification |
|---|
| 226 | { |
|---|
| 227 | // ① PaSoRi のノーティフィケーション通知を登録 |
|---|
| 228 | usbNotify = [[USBNotification usbNotificationWithClass:[USBDevicePasori class] |
|---|
| 229 | delegate:self |
|---|
| 230 | vendor:PASORI_USB_VENDOR |
|---|
| 231 | product:PASORI_USB_PRODUCT] retain]; |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | |
|---|
| 235 | /// ② USBコネクタにデバイスが挿入された |
|---|
| 236 | - (BOOL)usbAddDevice:(USBDevice *)usbDevice |
|---|
| 237 | { |
|---|
| 238 | if (usbPasori) |
|---|
| 239 | { // 既に使っているので新しく挿入されたデバイスは使わない |
|---|
| 240 | return NO; |
|---|
| 241 | } |
|---|
| 242 | |
|---|
| 243 | // あとで使う場合はインスタンス変数に登録しておく |
|---|
| 244 | usbPasori = (USBDevicePasori *)[usbDevice retain]; |
|---|
| 245 | |
|---|
| 246 | // 接続インディケータを変更 |
|---|
| 247 | [levelIndicator setIntValue:1]; |
|---|
| 248 | |
|---|
| 249 | // このデバイスは使うので YES を返す |
|---|
| 250 | return YES; |
|---|
| 251 | } |
|---|
| 252 | |
|---|
| 253 | |
|---|
| 254 | /// ③ USBコネクタからデバイスが抜かれた |
|---|
| 255 | - (void)usbRemoveDevice:(USBDevice *)usbDevice |
|---|
| 256 | { |
|---|
| 257 | if ([usbDevice isEqual:usbPasori]) |
|---|
| 258 | { // 使っているデバイスならば後始末をする |
|---|
| 259 | [usbPasori release]; |
|---|
| 260 | usbPasori = nil; |
|---|
| 261 | // 接続インディケータを変更 |
|---|
| 262 | [levelIndicator setIntValue:0]; |
|---|
| 263 | } |
|---|
| 264 | } |
|---|
| 265 | |
|---|
| 266 | |
|---|
| 267 | /// ④ PaSoRiからのイベント通知 |
|---|
| 268 | - (BOOL)pasoriEvent:(USBDevicePasori *)pasori state:(pasori_state_t)state |
|---|
| 269 | { |
|---|
| 270 | BOOL handled = NO; |
|---|
| 271 | |
|---|
| 272 | switch (state) |
|---|
| 273 | { |
|---|
| 274 | case PASORI_STATE_INITED: |
|---|
| 275 | // PaSoRiの初期化完了 |
|---|
| 276 | // 接続インディケータを変更 |
|---|
| 277 | [levelIndicator setIntValue:2]; |
|---|
| 278 | // ポーリングを行う |
|---|
| 279 | [pasori felicaPolling:FELICA_POLLING_SUICA]; |
|---|
| 280 | handled = YES; |
|---|
| 281 | break; |
|---|
| 282 | |
|---|
| 283 | case PASORI_STATE_POLLING_DONE: |
|---|
| 284 | // ポーリング成功 |
|---|
| 285 | // 接続インディケータを変更 |
|---|
| 286 | [levelIndicator setIntValue:3]; |
|---|
| 287 | // プログレスインディケータを開始 |
|---|
| 288 | [progressIndicator startAnimation:self]; |
|---|
| 289 | |
|---|
| 290 | // Suicaの表示データをクリア |
|---|
| 291 | [self suicaValueClean]; |
|---|
| 292 | // Suicaの履歴を読出す |
|---|
| 293 | [pasori felicaReadWithoutEncryption02:FELICA_SC_SUICA_VALUE]; |
|---|
| 294 | handled = YES; |
|---|
| 295 | break; |
|---|
| 296 | |
|---|
| 297 | case PASORI_STATE_READING_DONE: |
|---|
| 298 | // データの読出し終了 |
|---|
| 299 | if ([lookupSFCardFanDbCheckbox state] == 1) |
|---|
| 300 | { // 「IC SFCard Fan DB Service参照」チェックボックスが ON の場合 |
|---|
| 301 | // SFCard Fan DB Service から駅名を取得する |
|---|
| 302 | [self lookupSFCardFanDb]; |
|---|
| 303 | } |
|---|
| 304 | // プログレスインディケータを終了 |
|---|
| 305 | [progressIndicator stopAnimation:self]; |
|---|
| 306 | break; |
|---|
| 307 | |
|---|
| 308 | case PASORI_STATE_UNTOUCHED: |
|---|
| 309 | // カードがアンタッチされた |
|---|
| 310 | // 接続インディケータを変更 |
|---|
| 311 | [levelIndicator setIntValue:2]; |
|---|
| 312 | // 再度ポーリングを行う |
|---|
| 313 | [pasori felicaPolling:FELICA_POLLING_SUICA]; |
|---|
| 314 | // 実行中だったかもしれないのでプログレスインディケータを終了しておく |
|---|
| 315 | [progressIndicator stopAnimation:self]; |
|---|
| 316 | handled = YES; |
|---|
| 317 | break; |
|---|
| 318 | } |
|---|
| 319 | |
|---|
| 320 | return handled; |
|---|
| 321 | } |
|---|
| 322 | |
|---|
| 323 | |
|---|
| 324 | /// ⑤ Felicaデータの受信 |
|---|
| 325 | - (BOOL)felicaReceive:(USBDevicePasori *)pasori data:(felica_ans_t *)ans numBytesRead:(uint32_t)numBytesRead |
|---|
| 326 | { |
|---|
| 327 | BOOL handled = NO; |
|---|
| 328 | |
|---|
| 329 | switch (ans->as.normal.cmd) |
|---|
| 330 | { |
|---|
| 331 | case FELICA_ANS_READ_WITHOUT_ENCRYPTION: |
|---|
| 332 | { |
|---|
| 333 | PasoriRequest * currRequest = [pasori currRequest]; |
|---|
| 334 | // int serviceCode = [[[currRequest param] objectForKey:@"serviceCode"] unsignedShortValue]; |
|---|
| 335 | int index = [[[currRequest param] objectForKey:@"address"] unsignedShortValue]; |
|---|
| 336 | |
|---|
| 337 | // Suica 利用履歴のアップデート |
|---|
| 338 | [self updateSuicaValue:ans->as.read_wo_enc02.data index:index]; |
|---|
| 339 | } |
|---|
| 340 | handled = YES; |
|---|
| 341 | break; |
|---|
| 342 | } |
|---|
| 343 | |
|---|
| 344 | return handled; |
|---|
| 345 | } |
|---|
| 346 | |
|---|
| 347 | |
|---|
| 348 | /// ⑥ USBデータの受信 |
|---|
| 349 | - (BOOL)usbReceive:(USBDevicePasori *)pasori data:(uint8_t *)buffer numBytesRead:(uint32_t)numBytesRead |
|---|
| 350 | { |
|---|
| 351 | int i; |
|---|
| 352 | |
|---|
| 353 | for (i = 0; i < numBytesRead; i++) |
|---|
| 354 | { |
|---|
| 355 | printf("0x%02x, ", buffer[i]); |
|---|
| 356 | } |
|---|
| 357 | |
|---|
| 358 | printf("(len = %d)\n", numBytesRead); |
|---|
| 359 | |
|---|
| 360 | return YES; |
|---|
| 361 | } |
|---|
| 362 | |
|---|
| 363 | |
|---|
| 364 | #pragma mark - |
|---|
| 365 | |
|---|
| 366 | |
|---|
| 367 | /// サービスコードリストの要求 |
|---|
| 368 | - (IBAction)requestServiceCode:(id)sender |
|---|
| 369 | { |
|---|
| 370 | [usbPasori felicaRequestServiceCode]; |
|---|
| 371 | } |
|---|
| 372 | |
|---|
| 373 | |
|---|
| 374 | /// システムコードリストの要求 |
|---|
| 375 | - (IBAction)requestSystemCode:(id)sender |
|---|
| 376 | { |
|---|
| 377 | [usbPasori felicaRequestSystemCode]; |
|---|
| 378 | } |
|---|
| 379 | |
|---|
| 380 | |
|---|
| 381 | /// 「IC SFCard Fan DB Service参照」チェックボックスが変更されたときの処理 |
|---|
| 382 | - (IBAction)toggleLookupSFCard:(id)sender |
|---|
| 383 | { |
|---|
| 384 | if ([lookupSFCardFanDbCheckbox state] == 1) |
|---|
| 385 | { |
|---|
| 386 | // プログレスインディケータを開始 |
|---|
| 387 | [progressIndicator startAnimation:self]; |
|---|
| 388 | // SFCard Fan DB Service から駅名を取得する |
|---|
| 389 | [self lookupSFCardFanDb]; |
|---|
| 390 | // プログレスインディケータを終了 |
|---|
| 391 | [progressIndicator stopAnimation:self]; |
|---|
| 392 | } |
|---|
| 393 | } |
|---|
| 394 | |
|---|
| 395 | |
|---|
| 396 | /// SFCard Fan DB Service から駅名を取得する |
|---|
| 397 | - (void)lookupSFCardFanDb |
|---|
| 398 | { |
|---|
| 399 | int i; |
|---|
| 400 | |
|---|
| 401 | for (i = 0; i < [suicaValues count]; i++) |
|---|
| 402 | { |
|---|
| 403 | NSMutableDictionary * suicaValue = [suicaValues objectAtIndex:i]; |
|---|
| 404 | NSData * data = [suicaValue objectForKey:@"data"]; |
|---|
| 405 | suica_value_t * value = (suica_value_t *)[data bytes]; |
|---|
| 406 | |
|---|
| 407 | SFCardDB * sfCardDB = [SFCardDB sharedSFCardDB]; |
|---|
| 408 | |
|---|
| 409 | if ([[suicaValue objectForKey:@"in_station"] hasPrefix:@"0x"]) |
|---|
| 410 | { // 入場駅がコード表記のまま |
|---|
| 411 | NSDictionary * station1; |
|---|
| 412 | |
|---|
| 413 | station1 = [sfCardDB getStationNameWithStation:value->in_station regionCode:value->region]; |
|---|
| 414 | NSString * name1 = [station1 objectForKey:@"StationName"]; |
|---|
| 415 | if (! [name1 isEqual:[NSNull null]]) [suicaValue setObject:name1 forKey:@"in_station"]; |
|---|
| 416 | } |
|---|
| 417 | |
|---|
| 418 | if ([[suicaValue objectForKey:@"out_station"] hasPrefix:@"0x"]) |
|---|
| 419 | { // 出場駅がコード表記のまま |
|---|
| 420 | NSDictionary * station2; |
|---|
| 421 | |
|---|
| 422 | station2 = [sfCardDB getStationNameWithStation:value->out_station regionCode:value->region]; |
|---|
| 423 | NSString * name2 = [station2 objectForKey:@"StationName"]; |
|---|
| 424 | if (! [name2 isEqual:[NSNull null]]) [suicaValue setObject:name2 forKey:@"out_station"]; |
|---|
| 425 | } |
|---|
| 426 | |
|---|
| 427 | // 取得に時間がかかるので逐次表示を更新する |
|---|
| 428 | [suicaTableView display]; |
|---|
| 429 | } |
|---|
| 430 | |
|---|
| 431 | // [suicaTableView setNeedsDisplay:YES]; |
|---|
| 432 | } |
|---|
| 433 | |
|---|
| 434 | |
|---|
| 435 | #pragma mark - |
|---|
| 436 | |
|---|
| 437 | |
|---|
| 438 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView |
|---|
| 439 | { |
|---|
| 440 | return [suicaValues count]; |
|---|
| 441 | } |
|---|
| 442 | |
|---|
| 443 | |
|---|
| 444 | - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row |
|---|
| 445 | { |
|---|
| 446 | return [[suicaValues objectAtIndex:row] objectForKey:[tableColumn identifier]]; |
|---|
| 447 | } |
|---|
| 448 | |
|---|
| 449 | |
|---|
| 450 | |
|---|
| 451 | @end |
|---|