Просмотр исходного кода

Use the new global Calypso parser for contracts and events + add counter for navigo easy cards

DocSystem 1 год назад
Родитель
Сommit
5eb238a402
2 измененных файлов с 580 добавлено и 209 удалено
  1. 545 199
      scenes/metroflip_scene_navigo.c
  2. 35 10
      scenes/navigo.h

+ 545 - 199
scenes/metroflip_scene_navigo.c

@@ -1,6 +1,7 @@
 #include "../metroflip_i.h"
 #include <datetime.h>
 #include <dolphin/dolphin.h>
+#include <notification/notification_messages.h>
 #include <locale/locale.h>
 #include "navigo.h"
 
@@ -8,45 +9,6 @@
 
 #define TAG "Metroflip:Scene:Navigo"
 
-int eventSizes[] = {8,  24, 8, 8,   8,  8, 24, 16, 16, 8,  16, 16, 8,  16,
-                    16, 8,  5, 240, 16, 8, 16, 16, 16, 16, 16, 5,  16, 5};
-
-int* get_bit_positions(const char* binary_string, int* count) {
-    int length = strlen(binary_string);
-    int* positions = malloc(length * sizeof(int));
-    int pos_index = 0;
-
-    for(int i = 0; i < length; i++) {
-        if(binary_string[length - 1 - i] == '1') {
-            positions[pos_index++] = i - 1;
-        }
-    }
-
-    *count = pos_index;
-    return positions;
-}
-
-int is_event_present(int* array, int size, int number) {
-    for(int i = 0; i < size; i++) {
-        if(array[i] == number) {
-            return 1;
-        }
-    }
-    return 0;
-}
-
-int check_events(int* array, int size, int number) {
-    int total = 0;
-
-    for(int i = 0; i < size; i++) {
-        if(array[i] < number) {
-            total += eventSizes[array[i]];
-        }
-    }
-
-    return total + 53;
-}
-
 int select_new_app(
     int new_app,
     BitBuffer* tx_buffer,
@@ -153,6 +115,10 @@ const char* get_service_provider(int provider) {
         return "SNCF";
     case 3:
         return "RATP";
+    case 4:
+        return "IDF Mobilites";
+    case 10:
+        return "IDF Mobilites";
     case 115:
         return "CSO (VEOLIA)";
     case 116:
@@ -189,10 +155,73 @@ const char* get_transition_type(int transition) {
     case 15:
         return "Invalidation";
     default: {
-        char* transition_str = malloc(13);
-        snprintf(transition_str, 13, "Unknown (%d)", transition);
-        return transition_str;
+        return "Unknown";
+    }
     }
+}
+
+const char* get_navigo_type(int type) {
+    switch(type) {
+    case NAVIGO_EASY:
+        return "Navigo Easy";
+    case NAVIGO_DECOUVERTE:
+        return "Navigo Decouverte";
+    case NAVIGO_STANDARD:
+        return "Navigo Standard";
+    case NAVIGO_INTEGRAL:
+        return "Navigo Integral";
+    case IMAGINE_R:
+        return "Imagine R";
+    default:
+        return "Navigo Inconnu";
+    }
+}
+
+const char* get_tariff(int tariff) {
+    switch(tariff) {
+    case 0x0002:
+        return "Navigo Annuel";
+    case 0x0004:
+        return "Imagine R Junior";
+    case 0x0005:
+        return "Imagine R Etudiant";
+    case 0x000D:
+        return "Navigo Jeunes Week-end";
+    case 0x1000:
+        return "Navigo Liberte+";
+    case 0x5000:
+        return "Navigo Easy";
+    default:
+        return "Inconnu";
+    }
+}
+
+const char* get_pay_method(int pay_method) {
+    switch(pay_method) {
+    case 0x30:
+        return "Apple Pay";
+    case 0x80:
+        return "Debit PME";
+    case 0x90:
+        return "Espece";
+    case 0xA0:
+        return "Cheque mobilite";
+    case 0xB3:
+        return "Carte de paiement";
+    case 0xA4:
+        return "Cheque";
+    case 0xA5:
+        return "Cheque vacance";
+    case 0xB7:
+        return "Telepaiement";
+    case 0xD0:
+        return "Telereglement";
+    case 0xD7:
+        return "Bon de caisse, Versement prealable, Bon d’echange, Bon Voyage";
+    case 0xD9:
+        return "Bon de reduction";
+    default:
+        return "Inconnu";
     }
 }
 
@@ -293,17 +322,23 @@ void show_event_info(NavigoCardEvent* event, FuriString* parsed_data) {
         if(event->route_number_available) {
             furi_string_cat_printf(
                 parsed_data,
-                "RER %c\nStation : %s\n",
+                "RER %c\n%s\n",
                 (65 + event->route_number - 17),
-                get_train_station(event->station_group_id, event->station_id));
+                get_transition_type(event->transition));
         } else {
             furi_string_cat_printf(
                 parsed_data,
-                "%s %s\nStation : %s\n",
+                "%s %s\n%s\n",
                 get_transport_type(event->transport_type),
                 get_train_line(event->station_group_id),
-                get_train_station(event->station_group_id, event->station_id));
+                get_transition_type(event->transition));
         }
+        furi_string_cat_printf(
+            parsed_data, "Transporteur : %s\n", get_service_provider(event->service_provider));
+        furi_string_cat_printf(
+            parsed_data,
+            "Station : %s\n",
+            get_train_station(event->station_group_id, event->station_id));
         if(event->route_number_available) {
             furi_string_cat_printf(parsed_data, "Route : %d\n", event->route_number);
         }
@@ -354,11 +389,55 @@ void show_event_info(NavigoCardEvent* event, FuriString* parsed_data) {
     }
 }
 
-void show_contract_info(NavigoCardContract* contract, FuriString* parsed_data) {
-    furi_string_cat_printf(parsed_data, "Balance : %.2f EUR\n", (double)contract->balance);
-    furi_string_cat_printf(parsed_data, "Debut de validite:\n");
-    locale_format_datetime_cat(parsed_data, &contract->start_dt, false);
+void show_contract_info(NavigoCardContract* contract, int ticket_count, FuriString* parsed_data) {
+    furi_string_cat_printf(parsed_data, "Type : %s\n", get_tariff(contract->tariff));
+    if(contract->serial_number_available) {
+        furi_string_cat_printf(parsed_data, "Numero TCN : %d\n", contract->serial_number);
+    }
+    if(contract->pay_method_available) {
+        furi_string_cat_printf(
+            parsed_data, "Methode de paiement : %s\n", get_pay_method(contract->pay_method));
+    }
+    if(contract->price_amount_available) {
+        furi_string_cat_printf(parsed_data, "Montant : %.2f EUR\n", contract->price_amount);
+    }
+    if(contract->tariff == 0x5000) {
+        furi_string_cat_printf(parsed_data, "Titres restants : %d\n", ticket_count);
+    }
+    if(contract->end_date_available) {
+        furi_string_cat_printf(parsed_data, "Valide\ndu : ");
+        locale_format_datetime_cat(parsed_data, &contract->start_date, false);
+        furi_string_cat_printf(parsed_data, "\nau : ");
+        locale_format_datetime_cat(parsed_data, &contract->end_date, false);
+        furi_string_cat_printf(parsed_data, "\n");
+    } else {
+        furi_string_cat_printf(parsed_data, "Valide a partir du\n");
+        locale_format_datetime_cat(parsed_data, &contract->start_date, false);
+        furi_string_cat_printf(parsed_data, "\n");
+    }
+    if(contract->zones_available) {
+        furi_string_cat_printf(parsed_data, "Zones ");
+        for(int i = 0; i < 5; i++) {
+            if(contract->zones[i] != 0) {
+                furi_string_cat_printf(parsed_data, "%d", i + 1);
+                if(i < 4) {
+                    furi_string_cat_printf(parsed_data, ", ");
+                }
+            }
+        }
+        furi_string_cat_printf(parsed_data, "\n");
+    }
+    furi_string_cat_printf(parsed_data, "Vendu le : ");
+    locale_format_datetime_cat(parsed_data, &contract->sale_date, false);
     furi_string_cat_printf(parsed_data, "\n");
+    furi_string_cat_printf(
+        parsed_data,
+        "Agent de vente : %s (%d)\n",
+        get_service_provider(contract->sale_agent),
+        contract->sale_agent);
+    furi_string_cat_printf(parsed_data, "Terminal de vente : %d\n", contract->sale_device);
+    furi_string_cat_printf(parsed_data, "Etat : %d\n", contract->status);
+    furi_string_cat_printf(parsed_data, "Code authenticite : %d\n", contract->authenticator);
 }
 
 void show_environment_info(NavigoCardEnv* environment, FuriString* parsed_data) {
@@ -381,26 +460,32 @@ void show_environment_info(NavigoCardEnv* environment, FuriString* parsed_data)
 
 void update_page_info(NavigoContext* ctx, FuriString* parsed_data) {
     if(ctx->page_id == 0) {
-        furi_string_cat_printf(parsed_data, "\e#Navigo :\n");
-        furi_string_cat_printf(parsed_data, "\e#Contrat 1:\n");
-        show_contract_info(&ctx->card->contracts[0], parsed_data);
+        furi_string_cat_printf(
+            parsed_data, "\e#%s :\n", get_navigo_type(ctx->card->holder.card_status));
+        furi_string_cat_printf(parsed_data, "\e#Contrat 1 :\n");
+        show_contract_info(&ctx->card->contracts[0], ctx->card->ticket_count, parsed_data);
     } else if(ctx->page_id == 1) {
+        furi_string_cat_printf(
+            parsed_data, "\e#%s :\n", get_navigo_type(ctx->card->holder.card_status));
+        furi_string_cat_printf(parsed_data, "\e#Contrat 2 :\n");
+        show_contract_info(&ctx->card->contracts[1], ctx->card->ticket_count, parsed_data);
+    } else if(ctx->page_id == 2) {
         furi_string_cat_printf(parsed_data, "\e#Environnement :\n");
         show_environment_info(&ctx->card->environment, parsed_data);
-    } else if(ctx->page_id == 2) {
+    } else if(ctx->page_id == 3) {
         furi_string_cat_printf(parsed_data, "\e#Event 1 :\n");
         show_event_info(&ctx->card->events[0], parsed_data);
-    } else if(ctx->page_id == 3) {
+    } else if(ctx->page_id == 4) {
         furi_string_cat_printf(parsed_data, "\e#Event 2 :\n");
         show_event_info(&ctx->card->events[1], parsed_data);
-    } else if(ctx->page_id == 4) {
+    } else if(ctx->page_id == 5) {
         furi_string_cat_printf(parsed_data, "\e#Event 3 :\n");
         show_event_info(&ctx->card->events[2], parsed_data);
     }
 }
 
 void update_widget_elements(Widget* widget, NavigoContext* ctx, void* context) {
-    if(ctx->page_id < 4) {
+    if(ctx->page_id < 5) {
         widget_add_button_element(
             widget, GuiButtonTypeRight, "Next", metroflip_next_button_widget_callback, context);
     } else {
@@ -423,20 +508,24 @@ void metroflip_back_button_widget_callback(GuiButtonType result, InputType type,
     if(type == InputTypePress) {
         widget_reset(widget);
 
-        FuriString* parsed_data = furi_string_alloc();
-
         FURI_LOG_I(TAG, "Page ID: %d -> %d", ctx->page_id, ctx->page_id - 1);
 
         if(ctx->page_id > 0) {
+            if(ctx->page_id == 2 && ctx->card->contracts[1].tariff == 0) {
+                ctx->page_id -= 1;
+            }
             ctx->page_id -= 1;
         }
 
+        FuriString* parsed_data = furi_string_alloc();
+
         // Ensure no nested mutexes
         furi_mutex_acquire(ctx->mutex, FuriWaitForever);
         update_page_info(ctx, parsed_data);
         furi_mutex_release(ctx->mutex);
 
         widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+        // widget_add_icon_element(widget, 0, 0, &I_RFIDDolphinReceive_97x61);
 
         // Ensure no nested mutexes
         furi_mutex_acquire(ctx->mutex, FuriWaitForever);
@@ -457,11 +546,12 @@ void metroflip_next_button_widget_callback(GuiButtonType result, InputType type,
     if(type == InputTypePress) {
         widget_reset(widget);
 
-        FuriString* parsed_data = furi_string_alloc();
-
         FURI_LOG_I(TAG, "Page ID: %d -> %d", ctx->page_id, ctx->page_id + 1);
 
-        if(ctx->page_id < 4) {
+        if(ctx->page_id < 5) {
+            if(ctx->page_id == 0 && ctx->card->contracts[1].tariff == 0) {
+                ctx->page_id += 1;
+            }
             ctx->page_id += 1;
         } else {
             ctx->page_id = 0;
@@ -469,6 +559,8 @@ void metroflip_next_button_widget_callback(GuiButtonType result, InputType type,
                 app->scene_manager, MetroflipSceneStart);
         }
 
+        FuriString* parsed_data = furi_string_alloc();
+
         // Ensure no nested mutexes
         furi_mutex_acquire(ctx->mutex, FuriWaitForever);
         update_page_info(ctx, parsed_data);
@@ -485,6 +577,10 @@ void metroflip_next_button_widget_callback(GuiButtonType result, InputType type,
     }
 }
 
+void delay(int milliseconds) {
+    furi_thread_flags_wait(0, FuriFlagWaitAny, milliseconds);
+}
+
 static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event, void* context) {
     furi_assert(event.protocol == NfcProtocolIso14443_4b);
     NfcCommand next_command = NfcCommandContinue;
@@ -504,6 +600,11 @@ static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event,
 
     if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) {
         if(stage == MetroflipPollerEventTypeStart) {
+            // Start Flipper vibration
+            NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
+            notification_message(notification, &sequence_set_vibro_on);
+            delay(50);
+            notification_message(notification, &sequence_reset_vibro);
             nfc_device_set_data(
                 app->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(app->poller));
 
@@ -514,10 +615,10 @@ static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event,
                 NavigoCardData* card = malloc(sizeof(NavigoCardData));
 
                 // Initialize the card
-                card->contracts = malloc(sizeof(NavigoCardContract));
+                card->contracts = malloc(2 * sizeof(NavigoCardContract));
                 card->events = malloc(3 * sizeof(NavigoCardEvent));
 
-                // Select app for contract 1
+                // Select app for contracts
                 error =
                     select_new_app(0x20, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
                 if(error != 0) {
@@ -529,33 +630,240 @@ static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event,
                     break;
                 }
 
-                // read file 1
-                error = read_new_file(1, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
-                if(error != 0) {
+                // Prepare calypso structure
+                CalypsoApp* NavigoContractStructure = get_navigo_contract_structure();
+                if(!NavigoContractStructure) {
+                    FURI_LOG_E(TAG, "Failed to load Navigo Contract structure");
                     break;
                 }
 
-                // Check the response after reading the file
-                if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
-                    break;
-                }
+                // Now send the read command for contracts
+                for(size_t i = 1; i < 3; i++) {
+                    error =
+                        read_new_file(i, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
+                    if(error != 0) {
+                        break;
+                    }
 
-                char bit_representation[response_length * 8 + 1];
-                bit_representation[0] = '\0';
-                for(size_t i = 0; i < response_length; i++) {
-                    char bits[9];
-                    uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
-                    byte_to_binary(byte, bits);
-                    strlcat(bit_representation, bits, sizeof(bit_representation));
+                    // Check the response after reading the file
+                    if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
+                        break;
+                    }
+
+                    char bit_representation[response_length * 8 + 1];
+                    bit_representation[0] = '\0';
+                    for(size_t i = 0; i < response_length; i++) {
+                        char bits[9];
+                        uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
+                        byte_to_binary(byte, bits);
+                        strlcat(bit_representation, bits, sizeof(bit_representation));
+                    }
+                    bit_representation[response_length * 8] = '\0';
+
+                    /* int count = 0;
+                    int start = 0, end = NavigoContractStructure->elements[0].bitmap->size;
+                    char bit_slice[end - start + 1];
+                    strncpy(bit_slice, bit_representation + start, end - start);
+                    bit_slice[end - start] = '\0';
+                    int* positions = get_bit_positions(bit_slice, &count);
+
+                    FURI_LOG_I(TAG, "Contract %d bit positions: %d", i, count);
+
+                    // print positions
+                    for(int i = 0; i < count; i++) {
+                        char* key =
+                            (NavigoContractStructure->elements[0]
+                                         .bitmap->elements[positions[i]]
+                                         .type == CALYPSO_ELEMENT_TYPE_FINAL ?
+                                 NavigoContractStructure->elements[0]
+                                     .bitmap->elements[positions[i]]
+                                     .final->key :
+                                 NavigoContractStructure->elements[0]
+                                     .bitmap->elements[positions[i]]
+                                     .bitmap->key);
+                        int offset = get_calypso_node_offset(
+                            bit_representation, key, NavigoContractStructure);
+                        FURI_LOG_I(
+                            TAG, "Position: %d, Key: %s, Offset: %d", positions[i], key, offset);
+                    } */
+
+                    // 2. ContractTariff
+                    const char* contract_key = "ContractTariff";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
+                        card->contracts[i - 1].tariff =
+                            bit_slice_to_dec(bit_representation, start, end);
+                    }
+
+                    // 3. ContractSerialNumber
+                    contract_key = "ContractSerialNumber";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
+                        card->contracts[i - 1].serial_number =
+                            bit_slice_to_dec(bit_representation, start, end);
+                        card->contracts[i - 1].serial_number_available = true;
+                    }
+
+                    // 8. ContractPayMethod
+                    contract_key = "ContractPayMethod";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
+                        card->contracts[i - 1].pay_method =
+                            bit_slice_to_dec(bit_representation, start, end);
+                        card->contracts[i - 1].pay_method_available = true;
+                    }
+
+                    // 10. ContractPriceAmount
+                    contract_key = "ContractPriceAmount";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
+                        card->contracts[i - 1].price_amount =
+                            bit_slice_to_dec(bit_representation, start, end) / 100.0;
+                        card->contracts[i - 1].price_amount_available = true;
+                    }
+
+                    // 13.0. ContractValidityStartDate
+                    contract_key = "ContractValidityStartDate";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
+                        float decimal_value =
+                            bit_slice_to_dec(bit_representation, start, end) * 24 * 3600;
+                        uint64_t start_validity_timestamp = (decimal_value + (float)epoch) + 3600;
+                        datetime_timestamp_to_datetime(
+                            start_validity_timestamp, &card->contracts[i - 1].start_date);
+                    }
+
+                    // 13.2. ContractValidityEndDate
+                    contract_key = "ContractValidityEndDate";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
+                        float decimal_value =
+                            bit_slice_to_dec(bit_representation, start, end) * 24 * 3600;
+                        uint64_t end_validity_timestamp = (decimal_value + (float)epoch) + 3600;
+                        datetime_timestamp_to_datetime(
+                            end_validity_timestamp, &card->contracts[i - 1].end_date);
+                        card->contracts[i - 1].end_date_available = true;
+                    }
+
+                    // 13.6. ContractValidityZones
+                    contract_key = "ContractValidityZones";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int start = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        // binary form is 00011111 for zones 5, 4, 3, 2, 1
+                        for(int j = 0; j < 5; j++) {
+                            card->contracts[i - 1].zones[j] =
+                                bit_slice_to_dec(bit_representation, start + 3 + j, start + 3 + j);
+                        }
+                        card->contracts[i - 1].zones_available = true;
+                    }
+
+                    // 13.7. ContractValidityJourneys  -- pas sûr de le mettre lui
+
+                    // 15.0. ContractValiditySaleDate
+                    contract_key = "ContractValiditySaleDate";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
+                        float decimal_value =
+                            bit_slice_to_dec(bit_representation, start, end) * 24 * 3600;
+                        uint64_t sale_timestamp = (decimal_value + (float)epoch) + 3600;
+                        datetime_timestamp_to_datetime(
+                            sale_timestamp, &card->contracts[i - 1].sale_date);
+                    }
+
+                    // 15.2. ContractValiditySaleAgent
+                    contract_key = "ContractValiditySaleAgent";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
+                        card->contracts[i - 1].sale_agent =
+                            bit_slice_to_dec(bit_representation, start, end);
+                    } else {
+                        card->contracts[i - 1].sale_agent = -1;
+                    }
+
+                    // 15.3. ContractValiditySaleDevice
+                    contract_key = "ContractValiditySaleDevice";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
+                        card->contracts[i - 1].sale_device =
+                            bit_slice_to_dec(bit_representation, start, end);
+                    }
+
+                    // 16. ContractStatus  -- 0x1 ou 0xff
+                    contract_key = "ContractStatus";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
+                        card->contracts[i - 1].status =
+                            bit_slice_to_dec(bit_representation, start, end);
+                    }
+
+                    // 18. ContractAuthenticator
+                    contract_key = "ContractAuthenticator";
+                    if(is_calypso_node_present(
+                           bit_representation, contract_key, NavigoContractStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            bit_representation, contract_key, NavigoContractStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
+                        card->contracts[i - 1].authenticator =
+                            bit_slice_to_dec(bit_representation, start, end);
+                    }
                 }
-                bit_representation[response_length * 8] = '\0';
-                int start = 55, end = 70;
-                float decimal_value = bit_slice_to_dec(bit_representation, start, end);
-                card->contracts[0].balance = decimal_value / 100;
-                start = 80, end = 93;
-                decimal_value = bit_slice_to_dec(bit_representation, start, end);
-                uint64_t start_date_timestamp = (decimal_value * 24 * 3600) + (float)epoch + 3600;
-                datetime_timestamp_to_datetime(start_date_timestamp, &card->contracts[0].start_dt);
+
+                // Free the calypso structure
+                free_calypso_structure(NavigoContractStructure);
 
                 // Select app for environment
                 error = select_new_app(0x1, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
@@ -590,15 +898,13 @@ static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event,
                         bits,
                         sizeof(environment_bit_representation));
                 }
-                start = 0;
-                end = 5;
+                FURI_LOG_I(
+                    TAG, "Environment bit_representation: %s", environment_bit_representation);
+                int start = 0;
+                int end = 5;
                 card->environment.app_version =
                     bit_slice_to_dec(environment_bit_representation, start, end);
                 start = 13;
-                end = 36;
-                decimal_value = bit_slice_to_dec(environment_bit_representation, start, end);
-                FURI_LOG_I(TAG, "Network ID: %d", (int)decimal_value);
-                start = 13;
                 end = 16;
                 card->environment.country_num =
                     bit_slice_to_dec(environment_bit_representation, start, end) * 100 +
@@ -612,11 +918,59 @@ static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event,
                     bit_slice_to_dec(environment_bit_representation, start + 8, end + 8);
                 start = 45;
                 end = 58;
-                decimal_value = bit_slice_to_dec(environment_bit_representation, start, end);
+                float decimal_value = bit_slice_to_dec(environment_bit_representation, start, end);
                 uint64_t end_validity_timestamp =
                     (decimal_value * 24 * 3600) + (float)epoch + 3600;
                 datetime_timestamp_to_datetime(end_validity_timestamp, &card->environment.end_dt);
 
+                start = 95;
+                end = 98;
+                card->holder.card_status =
+                    bit_slice_to_dec(environment_bit_representation, start, end);
+
+                start = 99;
+                end = 104;
+                card->holder.commercial_id =
+                    bit_slice_to_dec(environment_bit_representation, start, end);
+
+                // Select app for counters (remaining tickets on Navigo Easy)
+                error =
+                    select_new_app(0x2A, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
+                if(error != 0) {
+                    break;
+                }
+
+                // Check the response after selecting app
+                if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
+                    break;
+                }
+
+                // read file 1
+                error = read_new_file(1, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
+                if(error != 0) {
+                    break;
+                }
+
+                // Check the response after reading the file
+                if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
+                    break;
+                }
+
+                char counter_bit_representation[response_length * 8 + 1];
+                counter_bit_representation[0] = '\0';
+                for(size_t i = 0; i < response_length; i++) {
+                    char bits[9];
+                    uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
+                    byte_to_binary(byte, bits);
+                    strlcat(counter_bit_representation, bits, sizeof(counter_bit_representation));
+                }
+                FURI_LOG_I(TAG, "Counter bit_representation: %s", counter_bit_representation);
+
+                // Ticket count
+                start = 2;
+                end = 5;
+                card->ticket_count = bit_slice_to_dec(counter_bit_representation, start, end);
+
                 // Select app for events
                 error =
                     select_new_app(0x10, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
@@ -629,8 +983,15 @@ static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event,
                     break;
                 }
 
+                // Load the calypso structure for events
+                CalypsoApp* NavigoEventStructure = get_navigo_event_structure();
+                if(!NavigoEventStructure) {
+                    FURI_LOG_E(TAG, "Failed to load Navigo Event structure");
+                    break;
+                }
+
                 // furi_string_cat_printf(parsed_data, "\e#Events :\n");
-                // Now send the read command
+                // Now send the read command for events
                 for(size_t i = 1; i < 4; i++) {
                     error =
                         read_new_file(i, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
@@ -651,183 +1012,168 @@ static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event,
                         byte_to_binary(byte, bits);
                         strlcat(event_bit_representation, bits, sizeof(event_bit_representation));
                     }
-                    FURI_LOG_I(
-                        TAG, "Event %d bit_representation: %s", i, event_bit_representation);
 
                     // furi_string_cat_printf(parsed_data, "Event 0%d :\n", i);
-                    int count = 0;
-                    int start = 25, end = 53;
+                    /* int count = 0;
+                    int start = 25, end = 52;
                     char bit_slice[end - start + 2];
                     strncpy(bit_slice, event_bit_representation + start, end - start + 1);
                     bit_slice[end - start + 1] = '\0';
                     int* positions = get_bit_positions(bit_slice, &count);
-                    /*FURI_LOG_I(TAG, "Positions: ");
+                    FURI_LOG_I(TAG, "Positions: ");
                     for(int i = 0; i < count; i++) {
                         FURI_LOG_I(TAG, "%d ", positions[i]);
-                    }*/
+                    } */
 
                     // 2. EventCode
-                    // 8 bits
-                    int event_number = 2;
-                    if(is_event_present(positions, count, event_number)) {
-                        int positionOffset = check_events(positions, count, event_number);
-                        int start = positionOffset, end = positionOffset + 7;
+                    const char* event_key = "EventCode";
+                    if(is_calypso_node_present(
+                           event_bit_representation, event_key, NavigoEventStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, NavigoEventStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(event_key, NavigoEventStructure) - 1;
                         int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
                         card->events[i - 1].transport_type = decimal_value >> 4;
                         card->events[i - 1].transition = decimal_value & 15;
-                        FURI_LOG_I(
-                            TAG,
-                            "%s - %s",
-                            TRANSPORT_LIST[card->events[i - 1].transport_type],
-                            TRANSITION_LIST[card->events[i - 1].transition]);
                     }
 
                     // 4. EventServiceProvider
-                    // 8 bits
-                    event_number = 4;
-                    if(is_event_present(positions, count, event_number)) {
-                        int positionOffset = check_events(positions, count, event_number);
-                        start = positionOffset, end = positionOffset + 7;
+                    event_key = "EventServiceProvider";
+                    if(is_calypso_node_present(
+                           event_bit_representation, event_key, NavigoEventStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, NavigoEventStructure);
+                        start = positionOffset,
+                        end = positionOffset +
+                              get_calypso_node_size(event_key, NavigoEventStructure) - 1;
                         card->events[i - 1].service_provider =
                             bit_slice_to_dec(event_bit_representation, start, end);
-                        FURI_LOG_I(
-                            TAG,
-                            "Transporteur : %s",
-                            SERVICE_PROVIDERS[card->events[i - 1].service_provider]);
                     }
 
                     // 8. EventLocationId
-                    // 16 bits
-                    event_number = 8;
-                    if(is_event_present(positions, count, event_number)) {
-                        int positionOffset = check_events(positions, count, event_number);
-                        start = positionOffset, end = positionOffset + 15;
+                    event_key = "EventLocationId";
+                    if(is_calypso_node_present(
+                           event_bit_representation, event_key, NavigoEventStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, NavigoEventStructure);
+                        start = positionOffset,
+                        end = positionOffset +
+                              get_calypso_node_size(event_key, NavigoEventStructure) - 1;
                         int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
                         card->events[i - 1].station_group_id = decimal_value >> 9;
                         card->events[i - 1].station_id = (decimal_value >> 4) & 31;
-                        if(card->events[i - 1].transport_type == METRO) {
-                            FURI_LOG_I(
-                                TAG,
-                                "Secteur %s - Station %s",
-                                METRO_STATION_LIST[card->events[i - 1].station_group_id][0],
-                                METRO_STATION_LIST[card->events[i - 1].station_group_id]
-                                                  [card->events[i - 1].station_id]);
-                        } else if(card->events[i - 1].transport_type == TRAIN) {
-                            FURI_LOG_I(
-                                TAG,
-                                "Ligne %s - Station %s",
-                                TRAIN_LINES_LIST[card->events[i - 1].station_group_id],
-                                TRAIN_STATION_LIST[card->events[i - 1].station_group_id]
-                                                  [card->events[i - 1].station_id]);
-                        } else {
-                            FURI_LOG_I(
-                                TAG,
-                                "Groupe ID %d - Station ID %d",
-                                card->events[i - 1].station_group_id,
-                                card->events[i - 1].station_id);
-                        }
                     }
 
                     // 9. EventLocationGate
-                    // 8 bits
-                    event_number = 9;
-                    if(is_event_present(positions, count, event_number)) {
-                        int positionOffset = check_events(positions, count, event_number);
-                        start = positionOffset, end = positionOffset + 7;
+                    event_key = "EventLocationGate";
+                    if(is_calypso_node_present(
+                           event_bit_representation, event_key, NavigoEventStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, NavigoEventStructure);
+                        start = positionOffset,
+                        end = positionOffset +
+                              get_calypso_node_size(event_key, NavigoEventStructure) - 1;
                         card->events[i - 1].location_gate =
                             bit_slice_to_dec(event_bit_representation, start, end);
                         card->events[i - 1].location_gate_available = true;
-                        FURI_LOG_I(TAG, "Passage : %d", card->events[i - 1].location_gate);
                     }
 
                     // 10. EventDevice
-                    // 16 bits
-                    event_number = 10;
-                    if(is_event_present(positions, count, event_number)) {
-                        int positionOffset = check_events(positions, count, event_number);
-                        start = positionOffset, end = positionOffset + 15;
+                    event_key = "EventDevice";
+                    if(is_calypso_node_present(
+                           event_bit_representation, event_key, NavigoEventStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, NavigoEventStructure);
+                        start = positionOffset,
+                        end = positionOffset +
+                              get_calypso_node_size(event_key, NavigoEventStructure) - 1;
                         int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
                         card->events[i - 1].device = decimal_value >> 8;
                         card->events[i - 1].door = card->events[i - 1].device / 2 + 1;
                         card->events[i - 1].side = card->events[i - 1].device % 2;
                         card->events[i - 1].device_available = true;
-                        const char* side = card->events[i - 1].side == 0 ? "droit" : "gauche";
-                        FURI_LOG_I(TAG, "Equipement : %d", card->events[i - 1].device);
-                        FURI_LOG_I(TAG, "Porte : %d - Côté %s", card->events[i - 1].door, side);
                     }
 
                     // 11. EventRouteNumber
-                    // 16 bits
-                    event_number = 11;
-                    if(is_event_present(positions, count, event_number)) {
-                        int positionOffset = check_events(positions, count, event_number);
-                        start = positionOffset, end = positionOffset + 15;
+                    event_key = "EventRouteNumber";
+                    if(is_calypso_node_present(
+                           event_bit_representation, event_key, NavigoEventStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, NavigoEventStructure);
+                        start = positionOffset,
+                        end = positionOffset +
+                              get_calypso_node_size(event_key, NavigoEventStructure) - 1;
                         card->events[i - 1].route_number =
                             bit_slice_to_dec(event_bit_representation, start, end);
                         card->events[i - 1].route_number_available = true;
-                        FURI_LOG_I(TAG, "Route : %d", card->events[i - 1].route_number);
                     }
 
                     // 13. EventJourneyRun
-                    // 16 bits
-                    event_number = 13;
-                    if(is_event_present(positions, count, event_number)) {
-                        int positionOffset = check_events(positions, count, event_number);
-                        start = positionOffset, end = positionOffset + 15;
+                    event_key = "EventJourneyRun";
+                    if(is_calypso_node_present(
+                           event_bit_representation, event_key, NavigoEventStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, NavigoEventStructure);
+                        start = positionOffset,
+                        end = positionOffset +
+                              get_calypso_node_size(event_key, NavigoEventStructure) - 1;
                         card->events[i - 1].mission =
                             bit_slice_to_dec(event_bit_representation, start, end);
                         card->events[i - 1].mission_available = true;
-                        FURI_LOG_I(TAG, "Mission : %d", card->events[i - 1].mission);
                     }
 
                     // 14. EventVehicleId
-                    // 16 bits
-                    event_number = 14;
-                    if(is_event_present(positions, count, event_number)) {
-                        int positionOffset = check_events(positions, count, event_number);
-                        start = positionOffset, end = positionOffset + 15;
+                    event_key = "EventVehicleId";
+                    if(is_calypso_node_present(
+                           event_bit_representation, event_key, NavigoEventStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, NavigoEventStructure);
+                        start = positionOffset,
+                        end = positionOffset +
+                              get_calypso_node_size(event_key, NavigoEventStructure) - 1;
                         card->events[i - 1].vehicle_id =
                             bit_slice_to_dec(event_bit_representation, start, end);
                         card->events[i - 1].vehicle_id_available = true;
-                        FURI_LOG_I(TAG, "Vehicule : %d", card->events[i - 1].vehicle_id);
                     }
 
                     // 25. EventContractPointer
-                    // 5 bits
-                    event_number = 25;
-                    if(is_event_present(positions, count, event_number)) {
-                        int positionOffset = check_events(positions, count, event_number);
-                        start = positionOffset, end = positionOffset + 4;
+                    event_key = "EventContractPointer";
+                    if(is_calypso_node_present(
+                           event_bit_representation, event_key, NavigoEventStructure)) {
+                        int positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, NavigoEventStructure);
+                        start = positionOffset,
+                        end = positionOffset +
+                              get_calypso_node_size(event_key, NavigoEventStructure) - 1;
                         card->events[i - 1].used_contract =
                             bit_slice_to_dec(event_bit_representation, start, end);
                         card->events[i - 1].used_contract_available = true;
-                        FURI_LOG_I(TAG, "Contrat : %d", card->events[i - 1].used_contract);
                     }
 
-                    free(positions);
-
-                    // EventDate
-                    // 14 bits
-                    start = 0, end = 13;
+                    // EventDateStamp
+                    event_key = "EventDateStamp";
+                    int positionOffset = get_calypso_node_offset(
+                        event_bit_representation, event_key, NavigoEventStructure);
+                    start = positionOffset,
+                    end = positionOffset + get_calypso_node_size(event_key, NavigoEventStructure) -
+                          1;
                     int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
                     uint64_t date_timestamp = (decimal_value * 24 * 3600) + epoch + 3600;
                     datetime_timestamp_to_datetime(date_timestamp, &card->events[i - 1].date);
 
-                    // EventTime
-                    // 11 bits
-                    start = 14, end = 24;
+                    // EventTimeStamp
+                    event_key = "EventTimeStamp";
+                    positionOffset = get_calypso_node_offset(
+                        event_bit_representation, event_key, NavigoEventStructure);
+                    start = positionOffset,
+                    end = positionOffset + get_calypso_node_size(event_key, NavigoEventStructure) -
+                          1;
                     decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
                     card->events[i - 1].date.hour = (decimal_value * 60) / 3600;
                     card->events[i - 1].date.minute = ((decimal_value * 60) % 3600) / 60;
                     card->events[i - 1].date.second = ((decimal_value * 60) % 3600) % 60;
-                    FURI_LOG_I(
-                        TAG,
-                        "Date : %02d/%02d/%04d %02dh%02d",
-                        card->events[i - 1].date.day,
-                        card->events[i - 1].date.month,
-                        card->events[i - 1].date.year,
-                        card->events[i - 1].date.hour,
-                        card->events[i - 1].date.minute);
                 }
                 UNUSED(TRANSITION_LIST);
                 UNUSED(TRANSPORT_LIST);

+ 35 - 10
scenes/navigo.h

@@ -1,6 +1,8 @@
 #include "../metroflip_i.h"
-#include <stdbool.h>
+#include "../api/calypso/calypso_util.h"
+#include "../api/calypso/cards/navigo.h"
 #include <datetime.h>
+#include <stdbool.h>
 
 #ifndef METRO_LIST_H
 #define METRO_LIST_H
@@ -42,14 +44,36 @@ typedef struct {
 } NavigoCardEnv;
 
 typedef struct {
-    float balance;
-    DateTime start_dt;
+    int card_status;
+    int commercial_id;
+} NavigoCardHolder;
+
+typedef struct {
+    int tariff;
+    int serial_number;
+    bool serial_number_available;
+    int pay_method;
+    bool pay_method_available;
+    double price_amount;
+    bool price_amount_available;
+    DateTime start_date;
+    DateTime end_date;
+    bool end_date_available;
+    int zones[5];
+    bool zones_available;
+    DateTime sale_date;
+    int sale_agent;
+    int sale_device;
+    int status;
+    int authenticator;
 } NavigoCardContract;
 
 typedef struct {
     NavigoCardEnv environment;
+    NavigoCardHolder holder;
     NavigoCardContract* contracts;
     NavigoCardEvent* events;
+    int ticket_count;
 } NavigoCardData;
 
 typedef struct {
@@ -60,13 +84,6 @@ typedef struct {
     FuriMutex* mutex;
 } NavigoContext;
 
-/* // Navigo Card Subscriptions Types
-static const char* SUBSCRIPTIONS_LIST[] = {
-    [1] = "Navigo decouverte",
-    [2] = "Navigo standard",
-    [6] = " Navigo integral",
-    [14] = "Imagine R (etudiant)"}; */
-
 // Service Providers
 static const char* SERVICE_PROVIDERS[] = {
     [2] = "SNCF",
@@ -94,6 +111,14 @@ typedef enum {
     PARKING = 8
 } TRANSPORT_TYPE;
 
+typedef enum {
+    NAVIGO_EASY = 0,
+    NAVIGO_DECOUVERTE = 1,
+    NAVIGO_STANDARD = 2,
+    NAVIGO_INTEGRAL = 6,
+    IMAGINE_R = 14
+} CARD_STATUS;
+
 // Transition Types
 static const char* TRANSITION_LIST[] = {
     [1] = "Validation en entree",