Procházet zdrojové kódy

Add special events support (like control/double validation)

DocSystem před 1 rokem
rodič
revize
3dc76ebd06

+ 2 - 0
api/calypso/calypso_i.h

@@ -29,6 +29,8 @@ typedef struct {
     unsigned int card_number;
 
     int contracts_count;
+    int events_count;
+    int special_events_count;
 } CalypsoCardData;
 
 typedef struct {

+ 25 - 0
api/calypso/cards/intercode.c

@@ -606,3 +606,28 @@ const char* get_intercode_string_transition_type(int transition) {
     }
     }
 }
+
+const char* get_intercode_string_event_result(int result) {
+    switch(result) {
+    case 0x0:
+        return "OK";
+    case 0x7:
+        return "Transfer / Dysfunction";
+    case 0x8:
+        return "Disabled due to fraud";
+    case 0x9:
+        return "Disabled due to monetary fraud";
+    case 0xA:
+        return "Invalidation impossible";
+    case 0x30:
+        return "Double validation";
+    default: {
+        char* result_str = malloc(6 * sizeof(char));
+        if(!result_str) {
+            return "Unknown";
+        }
+        snprintf(result_str, 6, "%d", result);
+        return result_str;
+    }
+    }
+}

+ 2 - 0
api/calypso/cards/intercode.h

@@ -13,6 +13,8 @@ CalypsoApp* get_intercode_structure_counter();
 
 const char* get_intercode_string_transition_type(int transition);
 
+const char* get_intercode_string_event_result(int result);
+
 typedef enum {
     URBAN_BUS = 1,
     INTERURBAN_BUS = 2,

+ 119 - 13
api/calypso/transit/navigo.c

@@ -5,9 +5,9 @@
 const char* get_navigo_transport_type(int type) {
     switch(type) {
     case URBAN_BUS:
-        return "Bus Urbain";
+        return "Urban Bus";
     case INTERURBAN_BUS:
-        return "Bus Interurbain";
+        return "Interurban Bus";
     case METRO:
         return "Metro";
     case TRAM:
@@ -16,6 +16,8 @@ const char* get_navigo_transport_type(int type) {
         return "Train";
     case PARKING:
         return "Parking";
+    case EXPRESS_COMMUTER_TRAIN:
+        return "TER";
     default:
         return "Unknown";
     }
@@ -23,23 +25,22 @@ const char* get_navigo_transport_type(int type) {
 
 const char* get_navigo_service_provider(int provider) {
     switch(provider) {
-    case 2:
+    case NAVIGO_PROVIDER_SNCF:
         return "SNCF";
-    case 3:
+    case NAVIGO_PROVIDER_RATP:
         return "RATP";
     case 4:
-        return "IDF Mobilites";
-    case 8:
-        return "ORA";
     case 10:
         return "IDF Mobilites";
-    case 115:
+    case NAVIGO_PROVIDER_ORA:
+        return "ORA";
+    case NAVIGO_PROVIDER_VEOLIA_CSO:
         return "CSO (VEOLIA)";
-    case 116:
+    case NAVIGO_PROVIDER_VEOLIA_RBUS:
         return "R'Bus (VEOLIA)";
-    case 156:
+    case NAVIGO_PROVIDER_PHEBUS:
         return "Phebus";
-    case 175:
+    case NAVIGO_PROVIDER_RATP_VEOLIA_NANTERRE:
         return "RATP (Veolia Transport Nanterre)";
     default: {
         char* provider_str = malloc(6 * sizeof(char));
@@ -129,7 +130,7 @@ bool is_ticket_count_available(int tariff) {
 const char* get_pay_method(int pay_method) {
     switch(pay_method) {
     case 0x30:
-        return "Apple Pay";
+        return "Apple Pay/Google Pay";
     case 0x80:
         return "Debit PME";
     case 0x90:
@@ -384,7 +385,7 @@ void show_navigo_event_info(
             furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
         }
         if(event->device_available) {
-            if(event->service_provider == 2) {
+            if(event->service_provider == NAVIGO_PROVIDER_SNCF) {
                 furi_string_cat_printf(parsed_data, "Device: %d\n", event->device & 0xFF);
             } else {
                 furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
@@ -441,6 +442,111 @@ void show_navigo_event_info(
     }
 }
 
+void show_navigo_special_event_info(NavigoCardSpecialEvent* event, FuriString* parsed_data) {
+    if(event->transport_type == URBAN_BUS || event->transport_type == INTERURBAN_BUS ||
+       event->transport_type == METRO || event->transport_type == TRAM) {
+        if(event->route_number_available) {
+            if(event->transport_type == METRO && event->route_number == 103) {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s 3 bis\n%s\n",
+                    get_navigo_transport_type(event->transport_type),
+                    get_intercode_string_transition_type(event->transition));
+            } else if(event->transport_type == TRAM) {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s %s\n%s\n",
+                    get_navigo_transport_type(event->transport_type),
+                    get_navigo_tram_line(event->route_number),
+                    get_intercode_string_transition_type(event->transition));
+            } else {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s %d\n%s\n",
+                    get_navigo_transport_type(event->transport_type),
+                    event->route_number,
+                    get_intercode_string_transition_type(event->transition));
+            }
+        } else {
+            furi_string_cat_printf(
+                parsed_data,
+                "%s\n%s\n",
+                get_navigo_transport_type(event->transport_type),
+                get_intercode_string_transition_type(event->transition));
+        }
+        furi_string_cat_printf(
+            parsed_data, "Result: %s\n", get_intercode_string_event_result(event->result));
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        furi_string_cat_printf(
+            parsed_data,
+            "Station: %s\nSector: %s\n",
+            get_navigo_station(event->station_group_id, event->station_id, event->service_provider),
+            get_navigo_station(event->station_group_id, 0, event->service_provider));
+        if(event->device_available) {
+            furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    } else if(event->transport_type == COMMUTER_TRAIN) {
+        if(event->route_number_available) {
+            furi_string_cat_printf(
+                parsed_data,
+                "RER %c\n%s\n",
+                (65 + event->route_number - 17),
+                get_intercode_string_transition_type(event->transition));
+        } else {
+            furi_string_cat_printf(
+                parsed_data,
+                "%s %s\n%s\n",
+                get_navigo_transport_type(event->transport_type),
+                get_navigo_sncf_train_line(event->station_group_id),
+                get_intercode_string_transition_type(event->transition));
+        }
+        furi_string_cat_printf(
+            parsed_data, "Result: %s\n", get_intercode_string_event_result(event->result));
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        furi_string_cat_printf(
+            parsed_data,
+            "Station: %s\n",
+            get_navigo_station(
+                event->station_group_id, event->station_id, event->service_provider));
+        if(event->device_available) {
+            if(event->service_provider == NAVIGO_PROVIDER_SNCF) {
+                furi_string_cat_printf(parsed_data, "Device: %d\n", event->device & 0xFF);
+            } else {
+                furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+            }
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    } else {
+        furi_string_cat_printf(
+            parsed_data,
+            "%s - %s\n",
+            get_navigo_transport_type(event->transport_type),
+            get_intercode_string_transition_type(event->transition));
+        furi_string_cat_printf(
+            parsed_data, "Result: %s\n", get_intercode_string_event_result(event->result));
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        furi_string_cat_printf(
+            parsed_data, "Station ID: %d-%d\n", event->station_group_id, event->station_id);
+        if(event->device_available) {
+            furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    }
+}
+
 void show_navigo_contract_info(NavigoCardContract* contract, FuriString* parsed_data) {
     furi_string_cat_printf(parsed_data, "Type: %s\n", get_navigo_tariff(contract->tariff));
     if(is_ticket_count_available(contract->tariff)) {

+ 3 - 1
api/calypso/transit/navigo.h

@@ -23,6 +23,8 @@ void show_navigo_event_info(
     NavigoCardContract* contracts,
     FuriString* parsed_data);
 
+void show_navigo_special_event_info(NavigoCardSpecialEvent* event, FuriString* parsed_data);
+
 void show_navigo_contract_info(NavigoCardContract* contract, FuriString* parsed_data);
 
 void show_navigo_environment_info(NavigoCardEnv* environment, FuriString* parsed_data);
@@ -43,7 +45,7 @@ typedef enum {
     NAVIGO_PROVIDER_VEOLIA_CSO = 115,
     NAVIGO_PROVIDER_VEOLIA_RBUS = 116,
     NAVIGO_PROVIDER_PHEBUS = 156,
-    NAVIGO_PROVIDER_RATP_VEOLIA_SERVICE = 175
+    NAVIGO_PROVIDER_RATP_VEOLIA_NANTERRE = 175
 } NAVIGO_SERVICE_PROVIDER;
 
 #endif // NAVIGO_H

+ 15 - 0
api/calypso/transit/navigo_i.h

@@ -27,6 +27,20 @@ typedef struct {
     DateTime date;
 } NavigoCardEvent;
 
+typedef struct {
+    int transport_type;
+    int transition;
+    int result;
+    int service_provider;
+    int station_group_id;
+    int station_id;
+    int device;
+    bool device_available;
+    int route_number;
+    bool route_number_available;
+    DateTime date;
+} NavigoCardSpecialEvent;
+
 typedef struct {
     int app_version;
     int country_num;
@@ -72,6 +86,7 @@ typedef struct {
     NavigoCardHolder holder;
     NavigoCardContract contracts[4];
     NavigoCardEvent events[3];
+    NavigoCardSpecialEvent special_events[3];
 } NavigoCardData;
 
 #endif // NAVIGO_I_H

+ 222 - 14
scenes/metroflip_scene_calypso.c

@@ -166,6 +166,21 @@ void update_page_info(void* context, FuriString* parsed_data) {
             break;
         }
         }
+    } else if(ctx->page_id == 8 || ctx->page_id == 9 || ctx->page_id == 10) {
+        furi_string_cat_printf(parsed_data, "\e#Special Event %d:\n", ctx->page_id - 7);
+        switch(ctx->card->card_type) {
+        case CALYPSO_CARD_NAVIGO: {
+            show_navigo_special_event_info(
+                &ctx->card->navigo->special_events[ctx->page_id - 8], parsed_data);
+            break;
+        }
+        case CALYPSO_CARD_OPUS: {
+            break;
+        }
+        default: {
+            break;
+        }
+        }
     }
 }
 
@@ -178,7 +193,7 @@ void update_widget_elements(void* context) {
             widget, GuiButtonTypeRight, "Exit", metroflip_next_button_widget_callback, context);
         return;
     }
-    if(ctx->page_id < 7) {
+    if(ctx->page_id < 10) {
         widget_add_button_element(
             widget, GuiButtonTypeRight, "Next", metroflip_next_button_widget_callback, context);
     } else {
@@ -204,6 +219,21 @@ void metroflip_back_button_widget_callback(GuiButtonType result, InputType type,
         FURI_LOG_I(TAG, "Page ID: %d -> %d", ctx->page_id, ctx->page_id - 1);
 
         if(ctx->page_id > 0) {
+            if(ctx->page_id == 10 && ctx->card->special_events_count < 2) {
+                ctx->page_id -= 1;
+            }
+            if(ctx->page_id == 9 && ctx->card->special_events_count < 1) {
+                ctx->page_id -= 1;
+            }
+            if(ctx->page_id == 8 && ctx->card->events_count < 3) {
+                ctx->page_id -= 1;
+            }
+            if(ctx->page_id == 7 && ctx->card->events_count < 2) {
+                ctx->page_id -= 1;
+            }
+            if(ctx->page_id == 6 && ctx->card->events_count < 1) {
+                ctx->page_id -= 1;
+            }
             if(ctx->page_id == 4 && ctx->card->contracts_count < 4) {
                 ctx->page_id -= 1;
             }
@@ -254,7 +284,7 @@ void metroflip_next_button_widget_callback(GuiButtonType result, InputType type,
                 app->scene_manager, MetroflipSceneStart);
             return;
         }
-        if(ctx->page_id < 7) {
+        if(ctx->page_id < 10) {
             if(ctx->page_id == 0 && ctx->card->contracts_count < 2) {
                 ctx->page_id += 1;
             }
@@ -264,6 +294,27 @@ void metroflip_next_button_widget_callback(GuiButtonType result, InputType type,
             if(ctx->page_id == 2 && ctx->card->contracts_count < 4) {
                 ctx->page_id += 1;
             }
+            if(ctx->page_id == 4 && ctx->card->events_count < 1) {
+                ctx->page_id += 1;
+            }
+            if(ctx->page_id == 5 && ctx->card->events_count < 2) {
+                ctx->page_id += 1;
+            }
+            if(ctx->page_id == 6 && ctx->card->events_count < 3) {
+                ctx->page_id += 1;
+            }
+            if(ctx->page_id == 7 && ctx->card->special_events_count < 1) {
+                ctx->page_id += 1;
+            }
+            if(ctx->page_id == 8 && ctx->card->special_events_count < 2) {
+                ctx->page_id += 1;
+            }
+            if(ctx->page_id == 9 && ctx->card->special_events_count < 3) {
+                ctx->page_id = 0;
+                scene_manager_search_and_switch_to_previous_scene(
+                    app->scene_manager, MetroflipSceneStart);
+                return;
+            }
             ctx->page_id += 1;
         } else {
             ctx->page_id = 0;
@@ -834,18 +885,6 @@ static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event,
                                 event_bit_representation, bits, sizeof(event_bit_representation));
                         }
 
-                        // furi_string_cat_printf(parsed_data, "Event 0%d :\n", i);
-                        /* 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: ");
-                    for(int i = 0; i < count; i++) {
-                        FURI_LOG_I(TAG, "%d ", positions[i]);
-                    } */
-
                         // 2. EventCode
                         const char* event_key = "EventCode";
                         if(is_calypso_node_present(
@@ -976,6 +1015,9 @@ static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event,
                             card->navigo->events[i - 1].used_contract =
                                 bit_slice_to_dec(event_bit_representation, start, end);
                             card->navigo->events[i - 1].used_contract_available = true;
+                            if(card->navigo->events[i - 1].used_contract > 0) {
+                                card->events_count++;
+                            }
                         }
 
                         // EventDateStamp
@@ -1005,6 +1047,172 @@ static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event,
                             ((decimal_value * 60) % 3600) % 60;
                     }
 
+                    // Select app for special events
+                    error = select_new_app(
+                        0x20, 0x40, 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;
+                    }
+
+                    // Now send the read command for special events
+                    for(size_t i = 1; i < 4; i++) {
+                        error = read_new_file(
+                            i, 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 event_bit_representation[response_length * 8 + 1];
+                        event_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(
+                                event_bit_representation, bits, sizeof(event_bit_representation));
+                        }
+
+                        if(bit_slice_to_dec(
+                               event_bit_representation,
+                               0,
+                               IntercodeEventStructure->container->elements[0].bitmap->size - 1) ==
+                           0) {
+                            break;
+                        } else {
+                            card->special_events_count++;
+                        }
+
+                        // 2. EventCode
+                        const char* event_key = "EventCode";
+                        if(is_calypso_node_present(
+                               event_bit_representation, event_key, IntercodeEventStructure)) {
+                            int positionOffset = get_calypso_node_offset(
+                                event_bit_representation, event_key, IntercodeEventStructure);
+                            int start = positionOffset,
+                                end = positionOffset +
+                                      get_calypso_node_size(event_key, IntercodeEventStructure) -
+                                      1;
+                            int decimal_value =
+                                bit_slice_to_dec(event_bit_representation, start, end);
+                            card->navigo->special_events[i - 1].transport_type = decimal_value >>
+                                                                                 4;
+                            card->navigo->special_events[i - 1].transition = decimal_value & 15;
+                        }
+
+                        // 3. EventResult
+                        event_key = "EventResult";
+                        if(is_calypso_node_present(
+                               event_bit_representation, event_key, IntercodeEventStructure)) {
+                            int positionOffset = get_calypso_node_offset(
+                                event_bit_representation, event_key, IntercodeEventStructure);
+                            int start = positionOffset,
+                                end = positionOffset +
+                                      get_calypso_node_size(event_key, IntercodeEventStructure) -
+                                      1;
+                            card->navigo->special_events[i - 1].result =
+                                bit_slice_to_dec(event_bit_representation, start, end);
+                        }
+
+                        // 4. EventServiceProvider
+                        event_key = "EventServiceProvider";
+                        if(is_calypso_node_present(
+                               event_bit_representation, event_key, IntercodeEventStructure)) {
+                            int positionOffset = get_calypso_node_offset(
+                                event_bit_representation, event_key, IntercodeEventStructure);
+                            int start = positionOffset,
+                                end = positionOffset +
+                                      get_calypso_node_size(event_key, IntercodeEventStructure) -
+                                      1;
+                            card->navigo->special_events[i - 1].service_provider =
+                                bit_slice_to_dec(event_bit_representation, start, end);
+                        }
+
+                        // 8. EventLocationId
+                        event_key = "EventLocationId";
+                        if(is_calypso_node_present(
+                               event_bit_representation, event_key, IntercodeEventStructure)) {
+                            int positionOffset = get_calypso_node_offset(
+                                event_bit_representation, event_key, IntercodeEventStructure);
+                            int start = positionOffset,
+                                end = positionOffset +
+                                      get_calypso_node_size(event_key, IntercodeEventStructure) -
+                                      1;
+                            int decimal_value =
+                                bit_slice_to_dec(event_bit_representation, start, end);
+                            card->navigo->special_events[i - 1].station_group_id = decimal_value >>
+                                                                                   9;
+                            card->navigo->special_events[i - 1].station_id = (decimal_value >> 4) &
+                                                                             31;
+                        }
+
+                        // 10. EventDevice
+                        event_key = "EventDevice";
+                        if(is_calypso_node_present(
+                               event_bit_representation, event_key, IntercodeEventStructure)) {
+                            int positionOffset = get_calypso_node_offset(
+                                event_bit_representation, event_key, IntercodeEventStructure);
+                            int start = positionOffset,
+                                end = positionOffset +
+                                      get_calypso_node_size(event_key, IntercodeEventStructure) -
+                                      1;
+                            int decimal_value =
+                                bit_slice_to_dec(event_bit_representation, start, end);
+                            card->navigo->special_events[i - 1].device = decimal_value;
+                        }
+
+                        // 11. EventRouteNumber
+                        event_key = "EventRouteNumber";
+                        if(is_calypso_node_present(
+                               event_bit_representation, event_key, IntercodeEventStructure)) {
+                            int positionOffset = get_calypso_node_offset(
+                                event_bit_representation, event_key, IntercodeEventStructure);
+                            int start = positionOffset,
+                                end = positionOffset +
+                                      get_calypso_node_size(event_key, IntercodeEventStructure) -
+                                      1;
+                            card->navigo->special_events[i - 1].route_number =
+                                bit_slice_to_dec(event_bit_representation, start, end);
+                            card->navigo->special_events[i - 1].route_number_available = true;
+                        }
+
+                        // EventDateStamp
+                        event_key = "EventDateStamp";
+                        int positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, IntercodeEventStructure);
+                        int start = positionOffset,
+                            end = positionOffset +
+                                  get_calypso_node_size(event_key, IntercodeEventStructure) - 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->navigo->special_events[i - 1].date);
+
+                        // EventTimeStamp
+                        event_key = "EventTimeStamp";
+                        positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, IntercodeEventStructure);
+                        start = positionOffset,
+                        end = positionOffset +
+                              get_calypso_node_size(event_key, IntercodeEventStructure) - 1;
+                        decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
+                        card->navigo->special_events[i - 1].date.hour =
+                            (decimal_value * 60) / 3600;
+                        card->navigo->special_events[i - 1].date.minute =
+                            ((decimal_value * 60) % 3600) / 60;
+                        card->navigo->special_events[i - 1].date.second =
+                            ((decimal_value * 60) % 3600) % 60;
+                    }
+
                     // Free the calypso structure
                     free_calypso_structure(IntercodeEventStructure);
                     break;