metroflip_scene_navigo.c 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345
  1. #include "../metroflip_i.h"
  2. #include <datetime.h>
  3. #include <dolphin/dolphin.h>
  4. #include <notification/notification_messages.h>
  5. #include <locale/locale.h>
  6. #include "navigo.h"
  7. #include <nfc/protocols/iso14443_4b/iso14443_4b_poller.h>
  8. #define TAG "Metroflip:Scene:Navigo"
  9. int select_new_app(
  10. int new_app_directory,
  11. int new_app,
  12. BitBuffer* tx_buffer,
  13. BitBuffer* rx_buffer,
  14. Iso14443_4bPoller* iso14443_4b_poller,
  15. Metroflip* app,
  16. MetroflipPollerEventType* stage) {
  17. select_app[5] = new_app_directory;
  18. select_app[6] = new_app;
  19. bit_buffer_reset(tx_buffer);
  20. bit_buffer_append_bytes(tx_buffer, select_app, sizeof(select_app));
  21. int error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  22. if(error != Iso14443_4bErrorNone) {
  23. FURI_LOG_I(TAG, "Select File: iso14443_4b_poller_send_block error %d", error);
  24. *stage = MetroflipPollerEventTypeFail;
  25. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventPollerFail);
  26. return error;
  27. }
  28. return 0;
  29. }
  30. int read_new_file(
  31. int new_file,
  32. BitBuffer* tx_buffer,
  33. BitBuffer* rx_buffer,
  34. Iso14443_4bPoller* iso14443_4b_poller,
  35. Metroflip* app,
  36. MetroflipPollerEventType* stage) {
  37. read_file[2] = new_file;
  38. bit_buffer_reset(tx_buffer);
  39. bit_buffer_append_bytes(tx_buffer, read_file, sizeof(read_file));
  40. Iso14443_4bError error =
  41. iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  42. if(error != Iso14443_4bErrorNone) {
  43. FURI_LOG_I(TAG, "Read File: iso14443_4b_poller_send_block error %d", error);
  44. *stage = MetroflipPollerEventTypeFail;
  45. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventPollerFail);
  46. return error;
  47. }
  48. return 0;
  49. }
  50. int check_response(
  51. BitBuffer* rx_buffer,
  52. Metroflip* app,
  53. MetroflipPollerEventType* stage,
  54. size_t* response_length) {
  55. *response_length = bit_buffer_get_size_bytes(rx_buffer);
  56. if(bit_buffer_get_byte(rx_buffer, *response_length - 2) != apdu_success[0] ||
  57. bit_buffer_get_byte(rx_buffer, *response_length - 1) != apdu_success[1]) {
  58. FURI_LOG_I(
  59. TAG,
  60. "Select profile app/file failed: %02x%02x",
  61. bit_buffer_get_byte(rx_buffer, *response_length - 2),
  62. bit_buffer_get_byte(rx_buffer, *response_length - 1));
  63. *stage = MetroflipPollerEventTypeFail;
  64. view_dispatcher_send_custom_event(
  65. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  66. return 1;
  67. }
  68. return 0;
  69. }
  70. const char* get_country(int country_num) {
  71. switch(country_num) {
  72. case 250:
  73. return "France";
  74. default:
  75. return "Unknown";
  76. }
  77. }
  78. const char* get_network(int network_num) {
  79. switch(network_num) {
  80. case 901:
  81. return "Ile-de-France Mobilites";
  82. default:
  83. return "Unknown";
  84. }
  85. }
  86. const char* get_transport_type(int type) {
  87. switch(type) {
  88. case BUS_URBAIN:
  89. return "Bus Urbain";
  90. case BUS_INTERURBAIN:
  91. return "Bus Interurbain";
  92. case METRO:
  93. return "Metro";
  94. case TRAM:
  95. return "Tram";
  96. case TRAIN:
  97. return "Train";
  98. case PARKING:
  99. return "Parking";
  100. default:
  101. return "Unknown";
  102. }
  103. }
  104. const char* get_service_provider(int provider) {
  105. switch(provider) {
  106. case 2:
  107. return "SNCF";
  108. case 3:
  109. return "RATP";
  110. case 4:
  111. return "IDF Mobilites";
  112. case 10:
  113. return "IDF Mobilites";
  114. case 115:
  115. return "CSO (VEOLIA)";
  116. case 116:
  117. return "R'Bus (VEOLIA)";
  118. case 156:
  119. return "Phebus";
  120. case 175:
  121. return "RATP (Veolia Transport Nanterre)";
  122. default:
  123. return "Unknown";
  124. }
  125. }
  126. const char* get_transition_type(int transition) {
  127. switch(transition) {
  128. case 1:
  129. return "Validation - entry";
  130. case 2:
  131. return "Validation - exit";
  132. case 4:
  133. return "Controle volant (a bord)";
  134. case 5:
  135. return "Test validation";
  136. case 6:
  137. return "Interchange validation - entry";
  138. case 7:
  139. return "Interchange validation - exit";
  140. case 9:
  141. return "Validation cancelled";
  142. case 10:
  143. return "Validation - entry";
  144. case 13:
  145. return "Distribution";
  146. case 15:
  147. return "Invalidation";
  148. default: {
  149. return "Unknown";
  150. }
  151. }
  152. }
  153. const char* get_navigo_type(int type) {
  154. switch(type) {
  155. case NAVIGO_EASY:
  156. return "Navigo Easy";
  157. case NAVIGO_DECOUVERTE:
  158. return "Navigo Decouverte";
  159. case NAVIGO_STANDARD:
  160. return "Navigo Standard";
  161. case NAVIGO_INTEGRAL:
  162. return "Navigo Integral";
  163. case IMAGINE_R:
  164. return "Imagine R";
  165. default:
  166. return "Navigo Inconnu";
  167. }
  168. }
  169. const char* get_tariff(int tariff) {
  170. switch(tariff) {
  171. case 0x0002:
  172. return "Navigo Annuel";
  173. case 0x0004:
  174. return "Imagine R Junior";
  175. case 0x0005:
  176. return "Imagine R Etudiant";
  177. case 0x000D:
  178. return "Navigo Jeunes Week-end";
  179. case 0x1000:
  180. return "Navigo Liberte+";
  181. case 0x5000:
  182. return "Navigo Easy";
  183. default:
  184. return "Inconnu";
  185. }
  186. }
  187. const char* get_pay_method(int pay_method) {
  188. switch(pay_method) {
  189. case 0x30:
  190. return "Apple Pay";
  191. case 0x80:
  192. return "Debit PME";
  193. case 0x90:
  194. return "Cash";
  195. case 0xA0:
  196. return "Mobility Check";
  197. case 0xB3:
  198. return "Payment Card";
  199. case 0xA4:
  200. return "Check";
  201. case 0xA5:
  202. return "Vacation Check";
  203. case 0xB7:
  204. return "Telepayment";
  205. case 0xD0:
  206. return "Remote Payment";
  207. case 0xD7:
  208. return "Voucher, Prepayment, Exchange Voucher, Travel Voucher";
  209. case 0xD9:
  210. return "Discount Voucher";
  211. default:
  212. return "Unknown";
  213. }
  214. }
  215. const char* get_metro_station(int station_group_id, int station_id) {
  216. // Use NAVIGO_H constants
  217. if(station_group_id < 32 && station_id < 16) {
  218. return METRO_STATION_LIST[station_group_id][station_id];
  219. }
  220. // cast station_group_id-station_id to a string
  221. char* station = malloc(12 * sizeof(char));
  222. if(!station) {
  223. return "Unknown";
  224. }
  225. snprintf(station, 10, "%d-%d", station_group_id, station_id);
  226. return station;
  227. }
  228. const char* get_train_line(int station_group_id) {
  229. if(station_group_id < 77) {
  230. return TRAIN_LINES_LIST[station_group_id];
  231. }
  232. return "Unknown";
  233. }
  234. const char* get_train_station(int station_group_id, int station_id) {
  235. if(station_group_id < 77 && station_id < 19) {
  236. return TRAIN_STATION_LIST[station_group_id][station_id];
  237. }
  238. // cast station_group_id-station_id to a string
  239. char* station = malloc(12 * sizeof(char));
  240. if(!station) {
  241. return "Unknown";
  242. }
  243. snprintf(station, 10, "%d-%d", station_group_id, station_id);
  244. return station;
  245. }
  246. void show_event_info(NavigoCardEvent* event, FuriString* parsed_data) {
  247. if(event->transport_type == BUS_URBAIN || event->transport_type == BUS_INTERURBAIN ||
  248. event->transport_type == METRO || event->transport_type == TRAM) {
  249. if(event->route_number_available) {
  250. if(event->transport_type == METRO && event->route_number == 103) {
  251. furi_string_cat_printf(
  252. parsed_data,
  253. "%s 3 bis\n%s\n",
  254. get_transport_type(event->transport_type),
  255. get_transition_type(event->transition));
  256. } else {
  257. furi_string_cat_printf(
  258. parsed_data,
  259. "%s %d\n%s\n",
  260. get_transport_type(event->transport_type),
  261. event->route_number,
  262. get_transition_type(event->transition));
  263. }
  264. } else {
  265. furi_string_cat_printf(
  266. parsed_data,
  267. "%s\n%s\n",
  268. get_transport_type(event->transport_type),
  269. get_transition_type(event->transition));
  270. }
  271. furi_string_cat_printf(
  272. parsed_data, "Transporter: %s\n", get_service_provider(event->service_provider));
  273. if(event->transport_type == METRO) {
  274. furi_string_cat_printf(
  275. parsed_data,
  276. "Station: %s\nSector: %s\n",
  277. get_metro_station(event->station_group_id, event->station_id),
  278. get_metro_station(event->station_group_id, 0));
  279. } else {
  280. furi_string_cat_printf(
  281. parsed_data, "Station ID: %d-%d\n", event->station_group_id, event->station_id);
  282. }
  283. if(event->location_gate_available) {
  284. furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
  285. }
  286. if(event->device_available) {
  287. if(event->transport_type == BUS_URBAIN || event->transport_type == BUS_INTERURBAIN) {
  288. const char* side = event->side == 0 ? "right" : "left";
  289. furi_string_cat_printf(parsed_data, "Door: %d\nSide: %s\n", event->door, side);
  290. } else {
  291. furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
  292. }
  293. }
  294. if(event->mission_available) {
  295. furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
  296. }
  297. if(event->vehicle_id_available) {
  298. furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
  299. }
  300. if(event->used_contract_available) {
  301. furi_string_cat_printf(parsed_data, "Contract: %d\n", event->used_contract);
  302. }
  303. locale_format_datetime_cat(parsed_data, &event->date, true);
  304. furi_string_cat_printf(parsed_data, "\n");
  305. } else if(event->transport_type == TRAIN) {
  306. if(event->route_number_available) {
  307. furi_string_cat_printf(
  308. parsed_data,
  309. "RER %c\n%s\n",
  310. (65 + event->route_number - 17),
  311. get_transition_type(event->transition));
  312. } else {
  313. furi_string_cat_printf(
  314. parsed_data,
  315. "%s %s\n%s\n",
  316. get_transport_type(event->transport_type),
  317. get_train_line(event->station_group_id),
  318. get_transition_type(event->transition));
  319. }
  320. furi_string_cat_printf(
  321. parsed_data, "Transporter: %s\n", get_service_provider(event->service_provider));
  322. furi_string_cat_printf(
  323. parsed_data,
  324. "Station: %s\n",
  325. get_train_station(event->station_group_id, event->station_id));
  326. if(event->route_number_available) {
  327. furi_string_cat_printf(parsed_data, "Route: %d\n", event->route_number);
  328. }
  329. if(event->location_gate_available) {
  330. furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
  331. }
  332. if(event->device_available) {
  333. furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
  334. }
  335. if(event->mission_available) {
  336. furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
  337. }
  338. if(event->vehicle_id_available) {
  339. furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
  340. }
  341. if(event->used_contract_available) {
  342. furi_string_cat_printf(parsed_data, "Contract: %d\n", event->used_contract);
  343. }
  344. locale_format_datetime_cat(parsed_data, &event->date, true);
  345. furi_string_cat_printf(parsed_data, "\n");
  346. } else {
  347. furi_string_cat_printf(
  348. parsed_data,
  349. "%s - %s\n",
  350. get_transport_type(event->transport_type),
  351. get_transition_type(event->transition));
  352. furi_string_cat_printf(
  353. parsed_data, "Transporter: %s\n", get_service_provider(event->service_provider));
  354. furi_string_cat_printf(
  355. parsed_data, "Station ID: %d-%d\n", event->station_group_id, event->station_id);
  356. if(event->location_gate_available) {
  357. furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
  358. }
  359. if(event->device_available) {
  360. furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
  361. }
  362. if(event->mission_available) {
  363. furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
  364. }
  365. if(event->vehicle_id_available) {
  366. furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
  367. }
  368. if(event->used_contract_available) {
  369. furi_string_cat_printf(parsed_data, "Contract: %d\n", event->used_contract);
  370. }
  371. locale_format_datetime_cat(parsed_data, &event->date, true);
  372. furi_string_cat_printf(parsed_data, "\n");
  373. }
  374. }
  375. void show_contract_info(NavigoCardContract* contract, int ticket_count, FuriString* parsed_data) {
  376. furi_string_cat_printf(parsed_data, "Type: %s\n", get_tariff(contract->tariff));
  377. if(contract->serial_number_available) {
  378. furi_string_cat_printf(parsed_data, "TCN Number: %d\n", contract->serial_number);
  379. }
  380. if(contract->pay_method_available) {
  381. furi_string_cat_printf(
  382. parsed_data, "Payment Method: %s\n", get_pay_method(contract->pay_method));
  383. }
  384. if(contract->price_amount_available) {
  385. furi_string_cat_printf(parsed_data, "Amount: %.2f EUR\n", contract->price_amount);
  386. }
  387. if(contract->tariff == 0x5000) {
  388. furi_string_cat_printf(parsed_data, "Remaining Tickets: %d\n", ticket_count);
  389. }
  390. if(contract->end_date_available) {
  391. furi_string_cat_printf(parsed_data, "Valid\nfrom: ");
  392. locale_format_datetime_cat(parsed_data, &contract->start_date, false);
  393. furi_string_cat_printf(parsed_data, "\nto: ");
  394. locale_format_datetime_cat(parsed_data, &contract->end_date, false);
  395. furi_string_cat_printf(parsed_data, "\n");
  396. } else {
  397. furi_string_cat_printf(parsed_data, "Valid from\n");
  398. locale_format_datetime_cat(parsed_data, &contract->start_date, false);
  399. furi_string_cat_printf(parsed_data, "\n");
  400. }
  401. if(contract->zones_available) {
  402. furi_string_cat_printf(parsed_data, "Zones ");
  403. for(int i = 0; i < 5; i++) {
  404. if(contract->zones[i] != 0) {
  405. furi_string_cat_printf(parsed_data, "%d", i + 1);
  406. if(i < 4) {
  407. furi_string_cat_printf(parsed_data, ", ");
  408. }
  409. }
  410. }
  411. furi_string_cat_printf(parsed_data, "\n");
  412. }
  413. furi_string_cat_printf(parsed_data, "Sold on: ");
  414. locale_format_datetime_cat(parsed_data, &contract->sale_date, false);
  415. furi_string_cat_printf(parsed_data, "\n");
  416. furi_string_cat_printf(
  417. parsed_data,
  418. "Sales Agent: %s (%d)\n",
  419. get_service_provider(contract->sale_agent),
  420. contract->sale_agent);
  421. furi_string_cat_printf(parsed_data, "Sales Terminal: %d\n", contract->sale_device);
  422. furi_string_cat_printf(parsed_data, "Status: %d\n", contract->status);
  423. furi_string_cat_printf(parsed_data, "Authenticity Code: %d\n", contract->authenticator);
  424. }
  425. void show_environment_info(NavigoCardEnv* environment, FuriString* parsed_data) {
  426. furi_string_cat_printf(parsed_data, "App Version: %d\n", environment->app_version);
  427. if(environment->country_num == 250) {
  428. furi_string_cat_printf(parsed_data, "Country: France\n");
  429. } else {
  430. furi_string_cat_printf(parsed_data, "Country: %d\n", environment->country_num);
  431. }
  432. if(environment->network_num == 901) {
  433. furi_string_cat_printf(parsed_data, "Network: Ile-de-France Mobilites\n");
  434. } else {
  435. furi_string_cat_printf(parsed_data, "Network: %d\n", environment->network_num);
  436. }
  437. furi_string_cat_printf(parsed_data, "End of validity:\n");
  438. locale_format_datetime_cat(parsed_data, &environment->end_dt, false);
  439. furi_string_cat_printf(parsed_data, "\n");
  440. }
  441. void update_page_info(void* context, FuriString* parsed_data) {
  442. Metroflip* app = context;
  443. NavigoContext* ctx = app->navigo_context;
  444. if(ctx->page_id == 0) {
  445. furi_string_cat_printf(
  446. parsed_data,
  447. "\e#%s %u:\n",
  448. get_navigo_type(ctx->card->holder.card_status),
  449. ctx->card->card_number);
  450. furi_string_cat_printf(parsed_data, "\e#Contract 1:\n");
  451. show_contract_info(&ctx->card->contracts[0], ctx->card->ticket_counts[0], parsed_data);
  452. } else if(ctx->page_id == 1) {
  453. furi_string_cat_printf(
  454. parsed_data,
  455. "\e#%s %u:\n",
  456. get_navigo_type(ctx->card->holder.card_status),
  457. ctx->card->card_number);
  458. furi_string_cat_printf(parsed_data, "\e#Contract 2:\n");
  459. show_contract_info(&ctx->card->contracts[1], ctx->card->ticket_counts[1], parsed_data);
  460. } else if(ctx->page_id == 2) {
  461. furi_string_cat_printf(parsed_data, "\e#Environment:\n");
  462. show_environment_info(&ctx->card->environment, parsed_data);
  463. } else if(ctx->page_id == 3) {
  464. furi_string_cat_printf(parsed_data, "\e#Event 1:\n");
  465. show_event_info(&ctx->card->events[0], parsed_data);
  466. } else if(ctx->page_id == 4) {
  467. furi_string_cat_printf(parsed_data, "\e#Event 2:\n");
  468. show_event_info(&ctx->card->events[1], parsed_data);
  469. } else if(ctx->page_id == 5) {
  470. furi_string_cat_printf(parsed_data, "\e#Event 3:\n");
  471. show_event_info(&ctx->card->events[2], parsed_data);
  472. }
  473. }
  474. void update_widget_elements(void* context) {
  475. Metroflip* app = context;
  476. NavigoContext* ctx = app->navigo_context;
  477. Widget* widget = app->widget;
  478. if(ctx->page_id < 5) {
  479. widget_add_button_element(
  480. widget, GuiButtonTypeRight, "Next", metroflip_next_button_widget_callback, context);
  481. } else {
  482. widget_add_button_element(
  483. widget, GuiButtonTypeRight, "Exit", metroflip_next_button_widget_callback, context);
  484. }
  485. if(ctx->page_id > 0) {
  486. widget_add_button_element(
  487. widget, GuiButtonTypeLeft, "Back", metroflip_back_button_widget_callback, context);
  488. }
  489. }
  490. void metroflip_back_button_widget_callback(GuiButtonType result, InputType type, void* context) {
  491. Metroflip* app = context;
  492. NavigoContext* ctx = app->navigo_context;
  493. UNUSED(result);
  494. Widget* widget = app->widget;
  495. if(type == InputTypePress) {
  496. widget_reset(widget);
  497. FURI_LOG_I(TAG, "Page ID: %d -> %d", ctx->page_id, ctx->page_id - 1);
  498. if(ctx->page_id > 0) {
  499. if(ctx->page_id == 2 && ctx->card->contracts[1].tariff == 0) {
  500. ctx->page_id -= 1;
  501. }
  502. ctx->page_id -= 1;
  503. }
  504. FuriString* parsed_data = furi_string_alloc();
  505. // Ensure no nested mutexes
  506. furi_mutex_acquire(ctx->mutex, FuriWaitForever);
  507. update_page_info(app, parsed_data);
  508. furi_mutex_release(ctx->mutex);
  509. widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  510. // widget_add_icon_element(widget, 0, 0, &I_RFIDDolphinReceive_97x61);
  511. // Ensure no nested mutexes
  512. furi_mutex_acquire(ctx->mutex, FuriWaitForever);
  513. update_widget_elements(app);
  514. furi_mutex_release(ctx->mutex);
  515. furi_string_free(parsed_data);
  516. }
  517. }
  518. void metroflip_next_button_widget_callback(GuiButtonType result, InputType type, void* context) {
  519. Metroflip* app = context;
  520. NavigoContext* ctx = app->navigo_context;
  521. UNUSED(result);
  522. Widget* widget = app->widget;
  523. if(type == InputTypePress) {
  524. widget_reset(widget);
  525. FURI_LOG_I(TAG, "Page ID: %d -> %d", ctx->page_id, ctx->page_id + 1);
  526. if(ctx->page_id < 5) {
  527. if(ctx->page_id == 0 && ctx->card->contracts[1].tariff == 0) {
  528. ctx->page_id += 1;
  529. }
  530. ctx->page_id += 1;
  531. } else {
  532. ctx->page_id = 0;
  533. scene_manager_search_and_switch_to_previous_scene(
  534. app->scene_manager, MetroflipSceneStart);
  535. return;
  536. }
  537. FuriString* parsed_data = furi_string_alloc();
  538. // Ensure no nested mutexes
  539. furi_mutex_acquire(ctx->mutex, FuriWaitForever);
  540. update_page_info(app, parsed_data);
  541. furi_mutex_release(ctx->mutex);
  542. widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  543. // Ensure no nested mutexes
  544. furi_mutex_acquire(ctx->mutex, FuriWaitForever);
  545. update_widget_elements(app);
  546. furi_mutex_release(ctx->mutex);
  547. furi_string_free(parsed_data);
  548. }
  549. }
  550. void delay(int milliseconds) {
  551. furi_thread_flags_wait(0, FuriFlagWaitAny, milliseconds);
  552. }
  553. static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event, void* context) {
  554. furi_assert(event.protocol == NfcProtocolIso14443_4b);
  555. NfcCommand next_command = NfcCommandContinue;
  556. MetroflipPollerEventType stage = MetroflipPollerEventTypeStart;
  557. Metroflip* app = context;
  558. FuriString* parsed_data = furi_string_alloc();
  559. Widget* widget = app->widget;
  560. furi_string_reset(app->text_box_store);
  561. const Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data;
  562. Iso14443_4bPoller* iso14443_4b_poller = event.instance;
  563. BitBuffer* tx_buffer = bit_buffer_alloc(Metroflip_POLLER_MAX_BUFFER_SIZE);
  564. BitBuffer* rx_buffer = bit_buffer_alloc(Metroflip_POLLER_MAX_BUFFER_SIZE);
  565. if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) {
  566. if(stage == MetroflipPollerEventTypeStart) {
  567. // Start Flipper vibration
  568. NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
  569. notification_message(notification, &sequence_set_vibro_on);
  570. delay(50);
  571. notification_message(notification, &sequence_reset_vibro);
  572. nfc_device_set_data(
  573. app->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(app->poller));
  574. Iso14443_4bError error;
  575. size_t response_length = 0;
  576. do {
  577. // Initialize the card data
  578. NavigoCardData* card = malloc(sizeof(NavigoCardData));
  579. // Select app ICC
  580. error = select_new_app(
  581. 0x00, 0x02, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  582. if(error != 0) {
  583. break;
  584. }
  585. // Check the response after selecting app
  586. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  587. break;
  588. }
  589. // Now send the read command for ICC
  590. error = read_new_file(0x01, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  591. if(error != 0) {
  592. break;
  593. }
  594. // Check the response after reading the file
  595. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  596. break;
  597. }
  598. char icc_bit_representation[response_length * 8 + 1];
  599. icc_bit_representation[0] = '\0';
  600. for(size_t i = 0; i < response_length; i++) {
  601. char bits[9];
  602. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  603. byte_to_binary(byte, bits);
  604. strlcat(icc_bit_representation, bits, sizeof(icc_bit_representation));
  605. }
  606. icc_bit_representation[response_length * 8] = '\0';
  607. FURI_LOG_I(TAG, "ICC bit representation: %s", icc_bit_representation);
  608. int start = 128, end = 159;
  609. card->card_number = bit_slice_to_dec(icc_bit_representation, start, end);
  610. // Select app for contracts
  611. error = select_new_app(
  612. 0x20, 0x20, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  613. if(error != 0) {
  614. break;
  615. }
  616. // Check the response after selecting app
  617. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  618. break;
  619. }
  620. // Prepare calypso structure
  621. CalypsoApp* NavigoContractStructure = get_navigo_contract_structure();
  622. if(!NavigoContractStructure) {
  623. FURI_LOG_E(TAG, "Failed to load Navigo Contract structure");
  624. break;
  625. }
  626. // Now send the read command for contracts
  627. for(size_t i = 1; i < 3; i++) {
  628. error =
  629. read_new_file(i, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  630. if(error != 0) {
  631. break;
  632. }
  633. // Check the response after reading the file
  634. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  635. break;
  636. }
  637. char bit_representation[response_length * 8 + 1];
  638. bit_representation[0] = '\0';
  639. for(size_t i = 0; i < response_length; i++) {
  640. char bits[9];
  641. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  642. byte_to_binary(byte, bits);
  643. strlcat(bit_representation, bits, sizeof(bit_representation));
  644. }
  645. bit_representation[response_length * 8] = '\0';
  646. /* int count = 0;
  647. int start = 0, end = NavigoContractStructure->elements[0].bitmap->size;
  648. char bit_slice[end - start + 1];
  649. strncpy(bit_slice, bit_representation + start, end - start);
  650. bit_slice[end - start] = '\0';
  651. int* positions = get_bit_positions(bit_slice, &count);
  652. FURI_LOG_I(TAG, "Contract %d bit positions: %d", i, count);
  653. // print positions
  654. for(int i = 0; i < count; i++) {
  655. char* key =
  656. (NavigoContractStructure->elements[0]
  657. .bitmap->elements[positions[i]]
  658. .type == CALYPSO_ELEMENT_TYPE_FINAL ?
  659. NavigoContractStructure->elements[0]
  660. .bitmap->elements[positions[i]]
  661. .final->key :
  662. NavigoContractStructure->elements[0]
  663. .bitmap->elements[positions[i]]
  664. .bitmap->key);
  665. int offset = get_calypso_node_offset(
  666. bit_representation, key, NavigoContractStructure);
  667. FURI_LOG_I(
  668. TAG, "Position: %d, Key: %s, Offset: %d", positions[i], key, offset);
  669. } */
  670. // 2. ContractTariff
  671. const char* contract_key = "ContractTariff";
  672. if(is_calypso_node_present(
  673. bit_representation, contract_key, NavigoContractStructure)) {
  674. int positionOffset = get_calypso_node_offset(
  675. bit_representation, contract_key, NavigoContractStructure);
  676. int start = positionOffset,
  677. end = positionOffset +
  678. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  679. card->contracts[i - 1].tariff =
  680. bit_slice_to_dec(bit_representation, start, end);
  681. }
  682. // 3. ContractSerialNumber
  683. contract_key = "ContractSerialNumber";
  684. if(is_calypso_node_present(
  685. bit_representation, contract_key, NavigoContractStructure)) {
  686. int positionOffset = get_calypso_node_offset(
  687. bit_representation, contract_key, NavigoContractStructure);
  688. int start = positionOffset,
  689. end = positionOffset +
  690. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  691. card->contracts[i - 1].serial_number =
  692. bit_slice_to_dec(bit_representation, start, end);
  693. card->contracts[i - 1].serial_number_available = true;
  694. }
  695. // 8. ContractPayMethod
  696. contract_key = "ContractPayMethod";
  697. if(is_calypso_node_present(
  698. bit_representation, contract_key, NavigoContractStructure)) {
  699. int positionOffset = get_calypso_node_offset(
  700. bit_representation, contract_key, NavigoContractStructure);
  701. int start = positionOffset,
  702. end = positionOffset +
  703. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  704. card->contracts[i - 1].pay_method =
  705. bit_slice_to_dec(bit_representation, start, end);
  706. card->contracts[i - 1].pay_method_available = true;
  707. }
  708. // 10. ContractPriceAmount
  709. contract_key = "ContractPriceAmount";
  710. if(is_calypso_node_present(
  711. bit_representation, contract_key, NavigoContractStructure)) {
  712. int positionOffset = get_calypso_node_offset(
  713. bit_representation, contract_key, NavigoContractStructure);
  714. int start = positionOffset,
  715. end = positionOffset +
  716. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  717. card->contracts[i - 1].price_amount =
  718. bit_slice_to_dec(bit_representation, start, end) / 100.0;
  719. card->contracts[i - 1].price_amount_available = true;
  720. }
  721. // 13.0. ContractValidityStartDate
  722. contract_key = "ContractValidityStartDate";
  723. if(is_calypso_node_present(
  724. bit_representation, contract_key, NavigoContractStructure)) {
  725. int positionOffset = get_calypso_node_offset(
  726. bit_representation, contract_key, NavigoContractStructure);
  727. int start = positionOffset,
  728. end = positionOffset +
  729. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  730. float decimal_value =
  731. bit_slice_to_dec(bit_representation, start, end) * 24 * 3600;
  732. uint64_t start_validity_timestamp = (decimal_value + (float)epoch) + 3600;
  733. datetime_timestamp_to_datetime(
  734. start_validity_timestamp, &card->contracts[i - 1].start_date);
  735. }
  736. // 13.2. ContractValidityEndDate
  737. contract_key = "ContractValidityEndDate";
  738. if(is_calypso_node_present(
  739. bit_representation, contract_key, NavigoContractStructure)) {
  740. int positionOffset = get_calypso_node_offset(
  741. bit_representation, contract_key, NavigoContractStructure);
  742. int start = positionOffset,
  743. end = positionOffset +
  744. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  745. float decimal_value =
  746. bit_slice_to_dec(bit_representation, start, end) * 24 * 3600;
  747. uint64_t end_validity_timestamp = (decimal_value + (float)epoch) + 3600;
  748. datetime_timestamp_to_datetime(
  749. end_validity_timestamp, &card->contracts[i - 1].end_date);
  750. card->contracts[i - 1].end_date_available = true;
  751. }
  752. // 13.6. ContractValidityZones
  753. contract_key = "ContractValidityZones";
  754. if(is_calypso_node_present(
  755. bit_representation, contract_key, NavigoContractStructure)) {
  756. int start = get_calypso_node_offset(
  757. bit_representation, contract_key, NavigoContractStructure);
  758. // binary form is 00011111 for zones 5, 4, 3, 2, 1
  759. for(int j = 0; j < 5; j++) {
  760. card->contracts[i - 1].zones[j] =
  761. bit_slice_to_dec(bit_representation, start + 3 + j, start + 3 + j);
  762. }
  763. card->contracts[i - 1].zones_available = true;
  764. }
  765. // 13.7. ContractValidityJourneys -- pas sûr de le mettre lui
  766. // 15.0. ContractValiditySaleDate
  767. contract_key = "ContractValiditySaleDate";
  768. if(is_calypso_node_present(
  769. bit_representation, contract_key, NavigoContractStructure)) {
  770. int positionOffset = get_calypso_node_offset(
  771. bit_representation, contract_key, NavigoContractStructure);
  772. int start = positionOffset,
  773. end = positionOffset +
  774. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  775. float decimal_value =
  776. bit_slice_to_dec(bit_representation, start, end) * 24 * 3600;
  777. uint64_t sale_timestamp = (decimal_value + (float)epoch) + 3600;
  778. datetime_timestamp_to_datetime(
  779. sale_timestamp, &card->contracts[i - 1].sale_date);
  780. }
  781. // 15.2. ContractValiditySaleAgent - FIX NEEDED
  782. contract_key = "ContractValiditySaleAgent";
  783. /* if(is_calypso_node_present(
  784. bit_representation, contract_key, NavigoContractStructure)) { */
  785. int positionOffset = get_calypso_node_offset(
  786. bit_representation, contract_key, NavigoContractStructure);
  787. int start = positionOffset,
  788. end = positionOffset +
  789. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  790. card->contracts[i - 1].sale_agent =
  791. bit_slice_to_dec(bit_representation, start, end);
  792. // }
  793. // 15.3. ContractValiditySaleDevice
  794. contract_key = "ContractValiditySaleDevice";
  795. if(is_calypso_node_present(
  796. bit_representation, contract_key, NavigoContractStructure)) {
  797. int positionOffset = get_calypso_node_offset(
  798. bit_representation, contract_key, NavigoContractStructure);
  799. int start = positionOffset,
  800. end = positionOffset +
  801. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  802. card->contracts[i - 1].sale_device =
  803. bit_slice_to_dec(bit_representation, start, end);
  804. }
  805. // 16. ContractStatus -- 0x1 ou 0xff
  806. contract_key = "ContractStatus";
  807. if(is_calypso_node_present(
  808. bit_representation, contract_key, NavigoContractStructure)) {
  809. int positionOffset = get_calypso_node_offset(
  810. bit_representation, contract_key, NavigoContractStructure);
  811. int start = positionOffset,
  812. end = positionOffset +
  813. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  814. card->contracts[i - 1].status =
  815. bit_slice_to_dec(bit_representation, start, end);
  816. }
  817. // 18. ContractAuthenticator
  818. contract_key = "ContractAuthenticator";
  819. if(is_calypso_node_present(
  820. bit_representation, contract_key, NavigoContractStructure)) {
  821. int positionOffset = get_calypso_node_offset(
  822. bit_representation, contract_key, NavigoContractStructure);
  823. int start = positionOffset,
  824. end = positionOffset +
  825. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  826. card->contracts[i - 1].authenticator =
  827. bit_slice_to_dec(bit_representation, start, end);
  828. }
  829. }
  830. // Free the calypso structure
  831. free_calypso_structure(NavigoContractStructure);
  832. // Select app for environment
  833. error = select_new_app(
  834. 0x20, 0x1, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  835. if(error != 0) {
  836. break;
  837. }
  838. // Check the response after selecting app
  839. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  840. break;
  841. }
  842. // read file 1
  843. error = read_new_file(1, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  844. if(error != 0) {
  845. break;
  846. }
  847. // Check the response after reading the file
  848. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  849. break;
  850. }
  851. char environment_bit_representation[response_length * 8 + 1];
  852. environment_bit_representation[0] = '\0';
  853. for(size_t i = 0; i < response_length; i++) {
  854. char bits[9];
  855. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  856. byte_to_binary(byte, bits);
  857. strlcat(
  858. environment_bit_representation,
  859. bits,
  860. sizeof(environment_bit_representation));
  861. }
  862. // FURI_LOG_I(
  863. // TAG, "Environment bit_representation: %s", environment_bit_representation);
  864. start = 0;
  865. end = 5;
  866. card->environment.app_version =
  867. bit_slice_to_dec(environment_bit_representation, start, end);
  868. start = 13;
  869. end = 16;
  870. card->environment.country_num =
  871. bit_slice_to_dec(environment_bit_representation, start, end) * 100 +
  872. bit_slice_to_dec(environment_bit_representation, start + 4, end + 4) * 10 +
  873. bit_slice_to_dec(environment_bit_representation, start + 8, end + 8);
  874. start = 25;
  875. end = 28;
  876. card->environment.network_num =
  877. bit_slice_to_dec(environment_bit_representation, start, end) * 100 +
  878. bit_slice_to_dec(environment_bit_representation, start + 4, end + 4) * 10 +
  879. bit_slice_to_dec(environment_bit_representation, start + 8, end + 8);
  880. start = 45;
  881. end = 58;
  882. float decimal_value = bit_slice_to_dec(environment_bit_representation, start, end);
  883. uint64_t end_validity_timestamp =
  884. (decimal_value * 24 * 3600) + (float)epoch + 3600;
  885. datetime_timestamp_to_datetime(end_validity_timestamp, &card->environment.end_dt);
  886. start = 95;
  887. end = 98;
  888. card->holder.card_status =
  889. bit_slice_to_dec(environment_bit_representation, start, end);
  890. start = 99;
  891. end = 104;
  892. card->holder.commercial_id =
  893. bit_slice_to_dec(environment_bit_representation, start, end);
  894. // Select app for counters (remaining tickets on Navigo Easy)
  895. error = select_new_app(
  896. 0x20, 0x69, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  897. if(error != 0) {
  898. break;
  899. }
  900. // Check the response after selecting app
  901. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  902. break;
  903. }
  904. // read file 1
  905. error = read_new_file(1, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  906. if(error != 0) {
  907. break;
  908. }
  909. // Check the response after reading the file
  910. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  911. break;
  912. }
  913. char counter_bit_representation[response_length * 8 + 1];
  914. counter_bit_representation[0] = '\0';
  915. for(size_t i = 0; i < response_length; i++) {
  916. char bits[9];
  917. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  918. byte_to_binary(byte, bits);
  919. strlcat(counter_bit_representation, bits, sizeof(counter_bit_representation));
  920. }
  921. // FURI_LOG_I(TAG, "Counter bit_representation: %s", counter_bit_representation);
  922. // Ticket count (contract 1)
  923. start = 0;
  924. end = 5;
  925. card->ticket_counts[0] = bit_slice_to_dec(counter_bit_representation, start, end);
  926. // Ticket count (contract 2)
  927. start = 24;
  928. end = 29;
  929. card->ticket_counts[1] = bit_slice_to_dec(counter_bit_representation, start, end);
  930. // FURI_LOG_I(TAG, "Ticket count 1: %d", card->ticket_counts[0]);
  931. // FURI_LOG_I(TAG, "Ticket count 2: %d", card->ticket_counts[1]);
  932. // Select app for events
  933. error = select_new_app(
  934. 0x20, 0x10, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  935. if(error != 0) {
  936. break;
  937. }
  938. // Check the response after selecting app
  939. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  940. break;
  941. }
  942. // Load the calypso structure for events
  943. CalypsoApp* NavigoEventStructure = get_navigo_event_structure();
  944. if(!NavigoEventStructure) {
  945. FURI_LOG_E(TAG, "Failed to load Navigo Event structure");
  946. break;
  947. }
  948. // furi_string_cat_printf(parsed_data, "\e#Events :\n");
  949. // Now send the read command for events
  950. for(size_t i = 1; i < 4; i++) {
  951. error =
  952. read_new_file(i, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  953. if(error != 0) {
  954. break;
  955. }
  956. // Check the response after reading the file
  957. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  958. break;
  959. }
  960. char event_bit_representation[response_length * 8 + 1];
  961. event_bit_representation[0] = '\0';
  962. for(size_t i = 0; i < response_length; i++) {
  963. char bits[9];
  964. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  965. byte_to_binary(byte, bits);
  966. strlcat(event_bit_representation, bits, sizeof(event_bit_representation));
  967. }
  968. // furi_string_cat_printf(parsed_data, "Event 0%d :\n", i);
  969. /* int count = 0;
  970. int start = 25, end = 52;
  971. char bit_slice[end - start + 2];
  972. strncpy(bit_slice, event_bit_representation + start, end - start + 1);
  973. bit_slice[end - start + 1] = '\0';
  974. int* positions = get_bit_positions(bit_slice, &count);
  975. FURI_LOG_I(TAG, "Positions: ");
  976. for(int i = 0; i < count; i++) {
  977. FURI_LOG_I(TAG, "%d ", positions[i]);
  978. } */
  979. // 2. EventCode
  980. const char* event_key = "EventCode";
  981. if(is_calypso_node_present(
  982. event_bit_representation, event_key, NavigoEventStructure)) {
  983. int positionOffset = get_calypso_node_offset(
  984. event_bit_representation, event_key, NavigoEventStructure);
  985. int start = positionOffset,
  986. end = positionOffset +
  987. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  988. int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
  989. card->events[i - 1].transport_type = decimal_value >> 4;
  990. card->events[i - 1].transition = decimal_value & 15;
  991. }
  992. // 4. EventServiceProvider
  993. event_key = "EventServiceProvider";
  994. if(is_calypso_node_present(
  995. event_bit_representation, event_key, NavigoEventStructure)) {
  996. int positionOffset = get_calypso_node_offset(
  997. event_bit_representation, event_key, NavigoEventStructure);
  998. start = positionOffset,
  999. end = positionOffset +
  1000. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1001. card->events[i - 1].service_provider =
  1002. bit_slice_to_dec(event_bit_representation, start, end);
  1003. }
  1004. // 8. EventLocationId
  1005. event_key = "EventLocationId";
  1006. if(is_calypso_node_present(
  1007. event_bit_representation, event_key, NavigoEventStructure)) {
  1008. int positionOffset = get_calypso_node_offset(
  1009. event_bit_representation, event_key, NavigoEventStructure);
  1010. start = positionOffset,
  1011. end = positionOffset +
  1012. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1013. int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
  1014. card->events[i - 1].station_group_id = decimal_value >> 9;
  1015. card->events[i - 1].station_id = (decimal_value >> 4) & 31;
  1016. }
  1017. // 9. EventLocationGate
  1018. event_key = "EventLocationGate";
  1019. if(is_calypso_node_present(
  1020. event_bit_representation, event_key, NavigoEventStructure)) {
  1021. int positionOffset = get_calypso_node_offset(
  1022. event_bit_representation, event_key, NavigoEventStructure);
  1023. start = positionOffset,
  1024. end = positionOffset +
  1025. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1026. card->events[i - 1].location_gate =
  1027. bit_slice_to_dec(event_bit_representation, start, end);
  1028. card->events[i - 1].location_gate_available = true;
  1029. }
  1030. // 10. EventDevice
  1031. event_key = "EventDevice";
  1032. if(is_calypso_node_present(
  1033. event_bit_representation, event_key, NavigoEventStructure)) {
  1034. int positionOffset = get_calypso_node_offset(
  1035. event_bit_representation, event_key, NavigoEventStructure);
  1036. start = positionOffset,
  1037. end = positionOffset +
  1038. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1039. int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
  1040. card->events[i - 1].device = decimal_value >> 8;
  1041. card->events[i - 1].door = card->events[i - 1].device / 2 + 1;
  1042. card->events[i - 1].side = card->events[i - 1].device % 2;
  1043. card->events[i - 1].device_available = true;
  1044. }
  1045. // 11. EventRouteNumber
  1046. event_key = "EventRouteNumber";
  1047. if(is_calypso_node_present(
  1048. event_bit_representation, event_key, NavigoEventStructure)) {
  1049. int positionOffset = get_calypso_node_offset(
  1050. event_bit_representation, event_key, NavigoEventStructure);
  1051. start = positionOffset,
  1052. end = positionOffset +
  1053. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1054. card->events[i - 1].route_number =
  1055. bit_slice_to_dec(event_bit_representation, start, end);
  1056. card->events[i - 1].route_number_available = true;
  1057. }
  1058. // 13. EventJourneyRun
  1059. event_key = "EventJourneyRun";
  1060. if(is_calypso_node_present(
  1061. event_bit_representation, event_key, NavigoEventStructure)) {
  1062. int positionOffset = get_calypso_node_offset(
  1063. event_bit_representation, event_key, NavigoEventStructure);
  1064. start = positionOffset,
  1065. end = positionOffset +
  1066. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1067. card->events[i - 1].mission =
  1068. bit_slice_to_dec(event_bit_representation, start, end);
  1069. card->events[i - 1].mission_available = true;
  1070. }
  1071. // 14. EventVehicleId
  1072. event_key = "EventVehicleId";
  1073. if(is_calypso_node_present(
  1074. event_bit_representation, event_key, NavigoEventStructure)) {
  1075. int positionOffset = get_calypso_node_offset(
  1076. event_bit_representation, event_key, NavigoEventStructure);
  1077. start = positionOffset,
  1078. end = positionOffset +
  1079. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1080. card->events[i - 1].vehicle_id =
  1081. bit_slice_to_dec(event_bit_representation, start, end);
  1082. card->events[i - 1].vehicle_id_available = true;
  1083. }
  1084. // 25. EventContractPointer
  1085. event_key = "EventContractPointer";
  1086. if(is_calypso_node_present(
  1087. event_bit_representation, event_key, NavigoEventStructure)) {
  1088. int positionOffset = get_calypso_node_offset(
  1089. event_bit_representation, event_key, NavigoEventStructure);
  1090. start = positionOffset,
  1091. end = positionOffset +
  1092. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1093. card->events[i - 1].used_contract =
  1094. bit_slice_to_dec(event_bit_representation, start, end);
  1095. card->events[i - 1].used_contract_available = true;
  1096. }
  1097. // EventDateStamp
  1098. event_key = "EventDateStamp";
  1099. int positionOffset = get_calypso_node_offset(
  1100. event_bit_representation, event_key, NavigoEventStructure);
  1101. start = positionOffset,
  1102. end = positionOffset + get_calypso_node_size(event_key, NavigoEventStructure) -
  1103. 1;
  1104. int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
  1105. uint64_t date_timestamp = (decimal_value * 24 * 3600) + epoch + 3600;
  1106. datetime_timestamp_to_datetime(date_timestamp, &card->events[i - 1].date);
  1107. // EventTimeStamp
  1108. event_key = "EventTimeStamp";
  1109. positionOffset = get_calypso_node_offset(
  1110. event_bit_representation, event_key, NavigoEventStructure);
  1111. start = positionOffset,
  1112. end = positionOffset + get_calypso_node_size(event_key, NavigoEventStructure) -
  1113. 1;
  1114. decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
  1115. card->events[i - 1].date.hour = (decimal_value * 60) / 3600;
  1116. card->events[i - 1].date.minute = ((decimal_value * 60) % 3600) / 60;
  1117. card->events[i - 1].date.second = ((decimal_value * 60) % 3600) % 60;
  1118. }
  1119. // Free the calypso structure
  1120. free_calypso_structure(NavigoEventStructure);
  1121. UNUSED(TRANSITION_LIST);
  1122. UNUSED(TRANSPORT_LIST);
  1123. UNUSED(SERVICE_PROVIDERS);
  1124. widget_add_text_scroll_element(
  1125. widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  1126. NavigoContext* context = malloc(sizeof(NavigoContext));
  1127. context->card = card;
  1128. context->page_id = 0;
  1129. context->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  1130. app->navigo_context = context;
  1131. // Ensure no nested mutexes
  1132. furi_mutex_acquire(context->mutex, FuriWaitForever);
  1133. update_page_info(app, parsed_data);
  1134. furi_mutex_release(context->mutex);
  1135. widget_add_text_scroll_element(
  1136. widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  1137. // Ensure no nested mutexes
  1138. furi_mutex_acquire(context->mutex, FuriWaitForever);
  1139. update_widget_elements(app);
  1140. furi_mutex_release(context->mutex);
  1141. furi_string_free(parsed_data);
  1142. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  1143. metroflip_app_blink_stop(app);
  1144. stage = MetroflipPollerEventTypeSuccess;
  1145. next_command = NfcCommandStop;
  1146. } while(false);
  1147. if(stage != MetroflipPollerEventTypeSuccess) {
  1148. next_command = NfcCommandStop;
  1149. }
  1150. }
  1151. }
  1152. bit_buffer_free(tx_buffer);
  1153. bit_buffer_free(rx_buffer);
  1154. return next_command;
  1155. }
  1156. void metroflip_scene_navigo_on_enter(void* context) {
  1157. Metroflip* app = context;
  1158. dolphin_deed(DolphinDeedNfcRead);
  1159. // Setup view
  1160. Popup* popup = app->popup;
  1161. popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
  1162. popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
  1163. // Start worker
  1164. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
  1165. nfc_scanner_alloc(app->nfc);
  1166. app->poller = nfc_poller_alloc(app->nfc, NfcProtocolIso14443_4b);
  1167. nfc_poller_start(app->poller, metroflip_scene_navigo_poller_callback, app);
  1168. metroflip_app_blink_start(app);
  1169. }
  1170. bool metroflip_scene_navigo_on_event(void* context, SceneManagerEvent event) {
  1171. Metroflip* app = context;
  1172. bool consumed = false;
  1173. if(event.type == SceneManagerEventTypeCustom) {
  1174. if(event.event == MetroflipPollerEventTypeCardDetect) {
  1175. Popup* popup = app->popup;
  1176. popup_set_header(popup, "Scanning..", 68, 30, AlignLeft, AlignTop);
  1177. consumed = true;
  1178. } else if(event.event == MetroflipCustomEventPollerFileNotFound) {
  1179. Popup* popup = app->popup;
  1180. popup_set_header(popup, "Read Error,\n wrong card", 68, 30, AlignLeft, AlignTop);
  1181. consumed = true;
  1182. } else if(event.event == MetroflipCustomEventPollerFail) {
  1183. Popup* popup = app->popup;
  1184. popup_set_header(popup, "Error, try\n again", 68, 30, AlignLeft, AlignTop);
  1185. consumed = true;
  1186. }
  1187. } else if(event.type == SceneManagerEventTypeBack) {
  1188. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  1189. consumed = true;
  1190. }
  1191. return consumed;
  1192. }
  1193. void metroflip_scene_navigo_on_exit(void* context) {
  1194. Metroflip* app = context;
  1195. if(app->poller) {
  1196. nfc_poller_stop(app->poller);
  1197. nfc_poller_free(app->poller);
  1198. }
  1199. metroflip_app_blink_stop(app);
  1200. widget_reset(app->widget);
  1201. // Clear view
  1202. popup_reset(app->popup);
  1203. if(app->navigo_context) {
  1204. NavigoContext* ctx = app->navigo_context;
  1205. free(ctx->card);
  1206. furi_mutex_free(ctx->mutex);
  1207. free(ctx);
  1208. app->navigo_context = NULL;
  1209. }
  1210. }