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

Merge branch 'dev' of https://github.com/luu176/Metroflip into dev

Luu 11 месяцев назад
Родитель
Сommit
72634d78fa

+ 2 - 0
api/calypso/calypso_i.h

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

+ 2 - 1
api/calypso/calypso_util.h

@@ -5,9 +5,10 @@
 #define CALYPSO_UTIL_H
 
 typedef enum {
+    CALYPSO_APP_ENV_HOLDER,
     CALYPSO_APP_CONTRACT,
     CALYPSO_APP_EVENT,
-    CALYPSO_APP_ENV_HOLDER,
+    CALYPSO_APP_COUNTER,
 } CalypsoAppType;
 
 typedef enum {

+ 248 - 159
api/calypso/cards/intercode.c

@@ -1,7 +1,174 @@
 #include <stdlib.h>
 #include "intercode.h"
 
-CalypsoApp* get_intercode_contract_structure() {
+CalypsoApp* get_intercode_structure_env_holder() {
+    CalypsoApp* IntercodeEnvHolderStructure = malloc(sizeof(CalypsoApp));
+    if(!IntercodeEnvHolderStructure) {
+        return NULL;
+    }
+
+    int app_elements_count = 3;
+
+    IntercodeEnvHolderStructure->type = CALYPSO_APP_ENV_HOLDER;
+    IntercodeEnvHolderStructure->container = malloc(sizeof(CalypsoContainerElement));
+    IntercodeEnvHolderStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    IntercodeEnvHolderStructure->container->size = app_elements_count;
+
+    IntercodeEnvHolderStructure->container->elements[0] = make_calypso_final_element(
+        "EnvApplicationVersionNumber",
+        6,
+        "Numéro de version de l’application Billettique",
+        CALYPSO_FINAL_TYPE_NUMBER);
+    IntercodeEnvHolderStructure->container->elements[1] = make_calypso_bitmap_element(
+        "Env",
+        7,
+        (CalypsoElement[]){
+            make_calypso_final_element(
+                "EnvNetworkId", 24, "Identification du réseau", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_final_element(
+                "EnvApplicationIssuerId",
+                8,
+                "Identification de l’émetteur de l’application",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EnvApplicationValidityEndDate",
+                14,
+                "Date de fin de validité de l’application",
+                CALYPSO_FINAL_TYPE_DATE),
+            make_calypso_final_element(
+                "EnvPayMethod", 11, "Code mode de paiement", CALYPSO_FINAL_TYPE_PAY_METHOD),
+            make_calypso_final_element(
+                "EnvAuthenticator",
+                16,
+                "Code de contrôle de l’intégrité des données",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EnvSelectList",
+                32,
+                "Bitmap de tableau de paramètre multiple",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_container_element(
+                "EnvData",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "EnvDataCardStatus", 1, "Statut de la carte", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EnvData2", 0, "Données complémentaires", CALYPSO_FINAL_TYPE_UNKNOWN),
+                }),
+        });
+    IntercodeEnvHolderStructure->container->elements[2] = make_calypso_bitmap_element(
+        "Holder",
+        8,
+        (CalypsoElement[]){
+            make_calypso_bitmap_element(
+                "HolderName",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "HolderSurname", 85, "Nom du porteur", CALYPSO_FINAL_TYPE_STRING),
+                    make_calypso_final_element(
+                        "HolderForename",
+                        85,
+                        "Prénom de naissance du porteur",
+                        CALYPSO_FINAL_TYPE_STRING),
+                }),
+            make_calypso_bitmap_element(
+                "HolderBirth",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "HolderBirthDate", 32, "Date de naissance", CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderBirthPlace",
+                        115,
+                        "Lieu de naissance (23 caractères)",
+                        CALYPSO_FINAL_TYPE_STRING),
+                }),
+            make_calypso_final_element(
+                "HolderBirthName",
+                85,
+                "Nom de naissance du porteur (17 caractères)",
+                CALYPSO_FINAL_TYPE_STRING),
+            make_calypso_final_element(
+                "HolderIdNumber", 32, "Identifiant Porteur", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_final_element(
+                "HolderCountryAlpha", 24, "Pays du titulaire", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderCompany", 32, "Société du titulaire", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_bitmap_element(
+                "HolderProfiles",
+                4,
+                (CalypsoElement[]){
+                    make_calypso_bitmap_element(
+                        "HolderProfileBitmap",
+                        3,
+                        (CalypsoElement[]){
+                            make_calypso_final_element(
+                                "HolderNetworkId", 24, "Réseau", CALYPSO_FINAL_TYPE_UNKNOWN),
+                            make_calypso_final_element(
+                                "HolderProfileNumber",
+                                8,
+                                "Numéro du statut",
+                                CALYPSO_FINAL_TYPE_NUMBER),
+                            make_calypso_final_element(
+                                "HolderProfileDate",
+                                14,
+                                "Date de fin de validité du statut",
+                                CALYPSO_FINAL_TYPE_DATE),
+                        }),
+                }),
+            make_calypso_bitmap_element(
+                "HolderData",
+                12,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "HolderDataCardStatus", 4, "Type de carte", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataTeleReglement", 4, "Télérèglement", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataResidence", 17, "Ville du domicile", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataCommercialID", 6, "Produit carte", CALYPSO_FINAL_TYPE_NUMBER),
+                    make_calypso_final_element(
+                        "HolderDataWorkPlace", 17, "Lieu de travail", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataStudyPlace", 17, "Lieu d'étude", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataSaleDevice",
+                        16,
+                        "Numéro logique de SAM",
+                        CALYPSO_FINAL_TYPE_NUMBER),
+                    make_calypso_final_element(
+                        "HolderDataAuthenticator", 16, "Signature", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate1",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate2",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate3",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate4",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                }),
+        });
+
+    return IntercodeEnvHolderStructure;
+}
+
+CalypsoApp* get_intercode_structure_contract() {
     CalypsoApp* IntercodeContractStructure = malloc(sizeof(CalypsoApp));
     if(!IntercodeContractStructure) {
         return NULL;
@@ -248,7 +415,7 @@ CalypsoApp* get_intercode_contract_structure() {
     return IntercodeContractStructure;
 }
 
-CalypsoApp* get_intercode_event_structure() {
+CalypsoApp* get_intercode_structure_event() {
     CalypsoApp* IntercodeEventStructure = malloc(sizeof(CalypsoApp));
     if(!IntercodeEventStructure) {
         return NULL;
@@ -382,169 +549,91 @@ CalypsoApp* get_intercode_event_structure() {
     return IntercodeEventStructure;
 }
 
-CalypsoApp* get_intercode_env_holder_structure() {
-    CalypsoApp* IntercodeEnvHolderStructure = malloc(sizeof(CalypsoApp));
-    if(!IntercodeEnvHolderStructure) {
+CalypsoApp* get_intercode_structure_counter() {
+    CalypsoApp* IntercodeCounterStructure = malloc(sizeof(CalypsoApp));
+    if(!IntercodeCounterStructure) {
         return NULL;
     }
 
-    int app_elements_count = 3;
+    int app_elements_count = 2;
 
-    IntercodeEnvHolderStructure->type = CALYPSO_APP_ENV_HOLDER;
-    IntercodeEnvHolderStructure->container = malloc(sizeof(CalypsoContainerElement));
-    IntercodeEnvHolderStructure->container->elements =
+    IntercodeCounterStructure->type = CALYPSO_APP_COUNTER;
+    IntercodeCounterStructure->container = malloc(sizeof(CalypsoContainerElement));
+    IntercodeCounterStructure->container->elements =
         malloc(app_elements_count * sizeof(CalypsoElement));
-    IntercodeEnvHolderStructure->container->size = app_elements_count;
+    IntercodeCounterStructure->container->size = app_elements_count;
 
-    IntercodeEnvHolderStructure->container->elements[0] = make_calypso_final_element(
-        "EnvApplicationVersionNumber",
-        6,
-        "Numéro de version de l’application Billettique",
+    IntercodeCounterStructure->container->elements[0] = make_calypso_final_element(
+        "CounterContractCount", 6, "Nombre de titres du carnet", CALYPSO_FINAL_TYPE_NUMBER);
+
+    IntercodeCounterStructure->container->elements[1] = make_calypso_final_element(
+        "CounterRelativeFirstStamp15mn",
+        18,
+        "Temps relatif de la première validation (au quart d'heure près)",
         CALYPSO_FINAL_TYPE_NUMBER);
-    IntercodeEnvHolderStructure->container->elements[1] = make_calypso_bitmap_element(
-        "Env",
-        7,
-        (CalypsoElement[]){
-            make_calypso_final_element(
-                "EnvNetworkId", 24, "Identification du réseau", CALYPSO_FINAL_TYPE_NUMBER),
-            make_calypso_final_element(
-                "EnvApplicationIssuerId",
-                8,
-                "Identification de l’émetteur de l’application",
-                CALYPSO_FINAL_TYPE_UNKNOWN),
-            make_calypso_final_element(
-                "EnvApplicationValidityEndDate",
-                14,
-                "Date de fin de validité de l’application",
-                CALYPSO_FINAL_TYPE_DATE),
-            make_calypso_final_element(
-                "EnvPayMethod", 11, "Code mode de paiement", CALYPSO_FINAL_TYPE_PAY_METHOD),
-            make_calypso_final_element(
-                "EnvAuthenticator",
-                16,
-                "Code de contrôle de l’intégrité des données",
-                CALYPSO_FINAL_TYPE_UNKNOWN),
-            make_calypso_final_element(
-                "EnvSelectList",
-                32,
-                "Bitmap de tableau de paramètre multiple",
-                CALYPSO_FINAL_TYPE_UNKNOWN),
-            make_calypso_container_element(
-                "EnvData",
-                2,
-                (CalypsoElement[]){
-                    make_calypso_final_element(
-                        "EnvDataCardStatus", 1, "Statut de la carte", CALYPSO_FINAL_TYPE_UNKNOWN),
-                    make_calypso_final_element(
-                        "EnvData2", 0, "Données complémentaires", CALYPSO_FINAL_TYPE_UNKNOWN),
-                }),
-        });
-    IntercodeEnvHolderStructure->container->elements[2] = make_calypso_bitmap_element(
-        "Holder",
-        8,
-        (CalypsoElement[]){
-            make_calypso_bitmap_element(
-                "HolderName",
-                2,
-                (CalypsoElement[]){
-                    make_calypso_final_element(
-                        "HolderSurname", 85, "Nom du porteur", CALYPSO_FINAL_TYPE_STRING),
-                    make_calypso_final_element(
-                        "HolderForename",
-                        85,
-                        "Prénom de naissance du porteur",
-                        CALYPSO_FINAL_TYPE_STRING),
-                }),
-            make_calypso_bitmap_element(
-                "HolderBirth",
-                2,
-                (CalypsoElement[]){
-                    make_calypso_final_element(
-                        "HolderBirthDate", 32, "Date de naissance", CALYPSO_FINAL_TYPE_DATE),
-                    make_calypso_final_element(
-                        "HolderBirthPlace",
-                        115,
-                        "Lieu de naissance (23 caractères)",
-                        CALYPSO_FINAL_TYPE_STRING),
-                }),
-            make_calypso_final_element(
-                "HolderBirthName",
-                85,
-                "Nom de naissance du porteur (17 caractères)",
-                CALYPSO_FINAL_TYPE_STRING),
-            make_calypso_final_element(
-                "HolderIdNumber", 32, "Identifiant Porteur", CALYPSO_FINAL_TYPE_NUMBER),
-            make_calypso_final_element(
-                "HolderCountryAlpha", 24, "Pays du titulaire", CALYPSO_FINAL_TYPE_UNKNOWN),
-            make_calypso_final_element(
-                "HolderCompany", 32, "Société du titulaire", CALYPSO_FINAL_TYPE_UNKNOWN),
-            make_calypso_bitmap_element(
-                "HolderProfiles",
-                4,
-                (CalypsoElement[]){
-                    make_calypso_bitmap_element(
-                        "HolderProfileBitmap",
-                        3,
-                        (CalypsoElement[]){
-                            make_calypso_final_element(
-                                "HolderNetworkId", 24, "Réseau", CALYPSO_FINAL_TYPE_UNKNOWN),
-                            make_calypso_final_element(
-                                "HolderProfileNumber",
-                                8,
-                                "Numéro du statut",
-                                CALYPSO_FINAL_TYPE_NUMBER),
-                            make_calypso_final_element(
-                                "HolderProfileDate",
-                                14,
-                                "Date de fin de validité du statut",
-                                CALYPSO_FINAL_TYPE_DATE),
-                        }),
-                }),
-            make_calypso_bitmap_element(
-                "HolderData",
-                12,
-                (CalypsoElement[]){
-                    make_calypso_final_element(
-                        "HolderDataCardStatus", 4, "Type de carte", CALYPSO_FINAL_TYPE_UNKNOWN),
-                    make_calypso_final_element(
-                        "HolderDataTeleReglement", 4, "Télérèglement", CALYPSO_FINAL_TYPE_UNKNOWN),
-                    make_calypso_final_element(
-                        "HolderDataResidence", 17, "Ville du domicile", CALYPSO_FINAL_TYPE_UNKNOWN),
-                    make_calypso_final_element(
-                        "HolderDataCommercialID", 6, "Produit carte", CALYPSO_FINAL_TYPE_NUMBER),
-                    make_calypso_final_element(
-                        "HolderDataWorkPlace", 17, "Lieu de travail", CALYPSO_FINAL_TYPE_UNKNOWN),
-                    make_calypso_final_element(
-                        "HolderDataStudyPlace", 17, "Lieu d'étude", CALYPSO_FINAL_TYPE_UNKNOWN),
-                    make_calypso_final_element(
-                        "HolderDataSaleDevice",
-                        16,
-                        "Numéro logique de SAM",
-                        CALYPSO_FINAL_TYPE_NUMBER),
-                    make_calypso_final_element(
-                        "HolderDataAuthenticator", 16, "Signature", CALYPSO_FINAL_TYPE_UNKNOWN),
-                    make_calypso_final_element(
-                        "HolderDataProfileStartDate1",
-                        14,
-                        "Date de début de validité du statut",
-                        CALYPSO_FINAL_TYPE_DATE),
-                    make_calypso_final_element(
-                        "HolderDataProfileStartDate2",
-                        14,
-                        "Date de début de validité du statut",
-                        CALYPSO_FINAL_TYPE_DATE),
-                    make_calypso_final_element(
-                        "HolderDataProfileStartDate3",
-                        14,
-                        "Date de début de validité du statut",
-                        CALYPSO_FINAL_TYPE_DATE),
-                    make_calypso_final_element(
-                        "HolderDataProfileStartDate4",
-                        14,
-                        "Date de début de validité du statut",
-                        CALYPSO_FINAL_TYPE_DATE),
-                }),
-        });
 
-    return IntercodeEnvHolderStructure;
+    return IntercodeCounterStructure;
+}
+
+const char* get_intercode_string_transition_type(int transition) {
+    switch(transition) {
+    case 0x1:
+        return "Entry (First validation)";
+    case 0x2:
+        return "Exit";
+    case 0x4:
+        return "Inspection";
+    case 0x5:
+        return "Test validation";
+    case 0x6:
+        return "Entry (Interchange)";
+    case 0x7:
+        return "Exit (Interchange)";
+    case 0x9:
+        return "Validation cancelled";
+    case 0xA:
+        return "Entry (Public road)";
+    case 0xB:
+        return "Exit (Public road)";
+    case 0xD:
+        return "Distribution";
+    case 0xF:
+        return "Invalidation";
+    default: {
+        char* transition_str = malloc(6 * sizeof(char));
+        snprintf(transition_str, 6, "%d", transition);
+        return transition_str;
+    }
+    }
+}
+
+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 (Entry)";
+    case 0x31:
+        return "Invalid zone";
+    case 0x32:
+        return "Contract expired";
+    case 0x33:
+        return "Double validation (Exit)";
+    default: {
+        char* result_str = malloc(6 * sizeof(char));
+        if(!result_str) {
+            return "Unknown";
+        }
+        snprintf(result_str, 6, "%d", result);
+        return result_str;
+    }
+    }
 }

+ 48 - 3
api/calypso/cards/intercode.h

@@ -3,10 +3,55 @@
 #ifndef INTERCODE_STRUCTURES_H
 #define INTERCODE_STRUCTURES_H
 
-CalypsoApp* get_intercode_contract_structure();
+CalypsoApp* get_intercode_structure_env_holder();
 
-CalypsoApp* get_intercode_event_structure();
+CalypsoApp* get_intercode_structure_contract();
 
-CalypsoApp* get_intercode_env_holder_structure();
+CalypsoApp* get_intercode_structure_event();
+
+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,
+    METRO = 3,
+    TRAM = 4,
+    COMMUTER_TRAIN = 5,
+    WATERBORNE_VEHICLE = 6,
+    TOLL = 7,
+    PARKING = 8,
+    TAXI = 9,
+    HIGH_SPEED_TRAIN = 10,
+    RURAL_BUS = 11,
+    EXPRESS_COMMUTER_TRAIN = 12,
+    PARA_TRANSIT = 13,
+    SELF_DRIVE_VEHICLE = 14,
+    COACH = 15,
+    LOCOMOTIVE = 16,
+    POWERED_MOTOR_VEHICLE = 17,
+    TRAILER = 18,
+    REGIONAL_TRAIN = 19,
+    INTER_CITY = 20,
+    FUNICULAR = 21,
+    CABLE_CAR = 22,
+    SELF_SERVICE_BICYCLE = 23,
+    CAR_SHARING = 24,
+    CAR_POOLING = 25,
+} INTERCODE_TRANSPORT_TYPE;
+
+typedef enum {
+    ENTRY = 1,
+    EXIT = 2,
+    PASSAGE = 3,
+    CHECKPOINT_INSPECTION = 4,
+    AUTONOMOUS = 5,
+    INTERCHANGE = 6,
+    VALIDATION = 7,
+    PRESENCE_DETECTED = 8,
+} INTERCODE_USER_ACTION;
 
 #endif // INTERCODE_STRUCTURES_H

+ 2 - 2
api/calypso/cards/opus.c

@@ -117,9 +117,9 @@ CalypsoApp* get_opus_event_structure() {
     OpusEventStructure->container->size = app_elements_count;
 
     OpusEventStructure->container->elements[0] =
-        make_calypso_final_element("EventDate", 14, "Event date", CALYPSO_FINAL_TYPE_DATE);
+        make_calypso_final_element("EventDateStamp", 14, "Event date", CALYPSO_FINAL_TYPE_DATE);
     OpusEventStructure->container->elements[1] =
-        make_calypso_final_element("EventTime", 11, "Event time", CALYPSO_FINAL_TYPE_TIME);
+        make_calypso_final_element("EventTimeStamp", 11, "Event time", CALYPSO_FINAL_TYPE_TIME);
     OpusEventStructure->container->elements[2] =
         make_calypso_final_element("EventUnknownX", 19, "Unknown X", CALYPSO_FINAL_TYPE_NUMBER);
     OpusEventStructure->container->elements[3] = make_calypso_bitmap_element(

+ 236 - 108
api/calypso/transit/navigo.c

@@ -4,18 +4,20 @@
 
 const char* get_navigo_transport_type(int type) {
     switch(type) {
-    case BUS_URBAIN:
-        return "Bus Urbain";
-    case BUS_INTERURBAIN:
-        return "Bus Interurbain";
+    case URBAN_BUS:
+        return "Urban Bus";
+    case INTERURBAN_BUS:
+        return "Interurban Bus";
     case METRO:
         return "Metro";
     case TRAM:
         return "Tram";
-    case TRAIN:
+    case COMMUTER_TRAIN:
         return "Train";
     case PARKING:
         return "Parking";
+    case EXPRESS_COMMUTER_TRAIN:
+        return "TER";
     default:
         return "Unknown";
     }
@@ -23,55 +25,27 @@ 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 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:
-        return "Unknown";
-    }
-}
-
-const char* get_transition_type(int transition) {
-    switch(transition) {
-    case 1:
-        return "Entry";
-    case 2:
-        return "Exit";
-    case 4:
-        return "Controle volant (a bord)";
-    case 5:
-        return "Test validation";
-    case 6:
-        return "Interchange - Entry";
-    case 7:
-        return "Interchange - Exit";
-    case 9:
-        return "Validation cancelled";
-    case 10:
-        return "Entry";
-    case 11:
-        return "Exit";
-    case 13:
-        return "Distribution";
-    case 15:
-        return "Invalidation";
     default: {
-        char* transition_str = malloc(6 * sizeof(char));
-        snprintf(transition_str, 6, "%d", transition);
-        return transition_str;
+        char* provider_str = malloc(6 * sizeof(char));
+        snprintf(provider_str, 6, "%d", provider);
+        return provider_str;
     }
     }
 }
@@ -156,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:
@@ -237,46 +211,113 @@ int get_intercode_subversion(int version) {
     return version & 0x07;
 }
 
-const char* get_navigo_metro_station(int station_group_id, int station_id) {
-    // Use NAVIGO_H constants
-    if(station_group_id < 32 && station_id < 16) {
-        return NAVIGO_METRO_STATION_LIST[station_group_id][station_id];
-    }
-    // cast station_group_id-station_id to a string
-    char* station = malloc(12 * sizeof(char));
-    if(!station) {
-        return "Unknown";
+char* get_token(char* psrc, const char* delimit, void* psave) {
+    static char sret[512];
+    register char* ptr = psave;
+    memset(sret, 0, sizeof(sret));
+
+    if(psrc != NULL) strcpy(ptr, psrc);
+    if(ptr == NULL) return NULL;
+
+    int i = 0, nlength = strlen(ptr);
+    for(i = 0; i < nlength; i++) {
+        if(ptr[i] == delimit[0]) break;
+        if(ptr[i] == delimit[1]) {
+            ptr = NULL;
+            break;
+        }
+        sret[i] = ptr[i];
     }
-    snprintf(station, 10, "%d-%d", station_group_id, station_id);
-    return station;
+    if(ptr != NULL) strcpy(ptr, &ptr[i + 1]);
+
+    return sret;
 }
 
-const char* get_navigo_train_line(int station_group_id) {
-    if(station_group_id < 77) {
-        return NAVIGO_TRAIN_LINES_LIST[station_group_id];
+char* get_navigo_station(
+    int station_group_id,
+    int station_id,
+    int station_sub_id,
+    int service_provider) {
+    switch(service_provider) {
+    case NAVIGO_PROVIDER_SNCF: {
+        if(station_group_id < 77 && station_id < 19) {
+            const char* station_group_name =
+                NAVIGO_SNCF_LOCATION_LIST[station_group_id][station_id];
+            if(station_group_name) {
+                // split station_name by '|' and return the station_sub_id - 1 element
+                char* station_name = strdup(station_group_name);
+                if(!station_name) {
+                    return "Unknown";
+                }
+                char* token = get_token(station_name, "|", station_name);
+                for(int i = 0; i < station_sub_id - 1; i++) {
+                    token = get_token(station_name, "|", station_name);
+                    if(!token) {
+                        break;
+                    }
+                }
+                free(station_name);
+                if(token) {
+                    return token;
+                }
+            }
+        }
+        // cast station_group_id-station_id-station_sub_id to a string
+        char* station = malloc(12 * sizeof(char));
+        if(!station) {
+            return "Unknown";
+        }
+        snprintf(station, 10, "%d-%d-%d", station_group_id, station_id, station_sub_id);
+        return station;
+    }
+    case NAVIGO_PROVIDER_RATP:
+    case NAVIGO_PROVIDER_ORA: {
+        if(station_group_id < 32 && station_id < 16) {
+            const char* station_name = NAVIGO_RATP_LOCATION_LIST[station_group_id][station_id];
+            if(station_name) {
+                char* station;
+                if((station = strdup(station_name)) != NULL) {
+                    return station;
+                }
+            }
+        }
+        // cast station_group_id-station_id to a string
+        char* station = malloc(12 * sizeof(char));
+        if(!station) {
+            return "Unknown";
+        }
+        snprintf(station, 10, "%d-%d", station_group_id, station_id);
+        return station;
+    }
+    default: {
+        // cast station_group_id-station_id to a string
+        char* station = malloc(12 * sizeof(char));
+        if(!station) {
+            return "Unknown";
+        }
+        snprintf(station, 10, "%d-%d", station_group_id, station_id);
+        return station;
+    }
     }
-    return "Unknown";
 }
 
-const char* get_navigo_train_station(int station_group_id, int station_id) {
-    if(station_group_id < 77 && station_id < 19) {
-        return NAVIGO_TRAIN_STATION_LIST[station_group_id][station_id];
-    }
-    // cast station_group_id-station_id to a string
-    char* station = malloc(12 * sizeof(char));
-    if(!station) {
-        return "Unknown";
+const char* get_navigo_sncf_train_line(int station_group_id) {
+    if(station_group_id < 77) {
+        return NAVIGO_SNCF_TRAIN_LINES_LIST[station_group_id];
     }
-    snprintf(station, 10, "%d-%d", station_group_id, station_id);
-    return station;
+    return "Unknown";
 }
 
 const char* get_navigo_tram_line(int route_number) {
     switch(route_number) {
     case 1:
         return "T3a";
+    case 9:
+        return "T9";
     case 16:
         return "T6";
+    case 18:
+        return "T8";
     default: {
         char* line = malloc(5 * sizeof(char));
         if(!line) {
@@ -296,7 +337,11 @@ void show_navigo_event_info(
         furi_string_cat_printf(parsed_data, "No event data\n");
         return;
     }
-    if(event->transport_type == BUS_URBAIN || event->transport_type == BUS_INTERURBAIN ||
+    char* station = get_navigo_station(
+        event->station_group_id, event->station_id, event->station_sub_id, event->service_provider);
+    char* sector = get_navigo_station(event->station_group_id, 0, 0, event->service_provider);
+
+    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) {
@@ -304,48 +349,39 @@ void show_navigo_event_info(
                     parsed_data,
                     "%s 3 bis\n%s\n",
                     get_navigo_transport_type(event->transport_type),
-                    get_transition_type(event->transition));
+                    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_transition_type(event->transition));
+                    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_transition_type(event->transition));
+                    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_transition_type(event->transition));
+                get_intercode_string_transition_type(event->transition));
         }
         furi_string_cat_printf(
             parsed_data,
             "Transporter: %s\n",
             get_navigo_service_provider(event->service_provider));
-        if(event->transport_type == METRO) {
-            furi_string_cat_printf(
-                parsed_data,
-                "Station: %s\nSector: %s\n",
-                get_navigo_metro_station(event->station_group_id, event->station_id),
-                get_navigo_metro_station(event->station_group_id, 0));
-        } else {
-            furi_string_cat_printf(
-                parsed_data, "Station ID: %d-%d\n", event->station_group_id, event->station_id);
-        }
+        furi_string_cat_printf(parsed_data, "Station: %s\nSector: %s\n", station, sector);
         if(event->location_gate_available) {
             furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
         }
         if(event->device_available) {
-            if(event->transport_type == BUS_URBAIN || event->transport_type == BUS_INTERURBAIN) {
+            if(event->transport_type == URBAN_BUS || event->transport_type == INTERURBAN_BUS) {
                 const char* side = event->side == 0 ? "right" : "left";
                 furi_string_cat_printf(parsed_data, "Door: %d\nSide: %s\n", event->door, side);
             } else {
@@ -367,44 +403,31 @@ void show_navigo_event_info(
         }
         locale_format_datetime_cat(parsed_data, &event->date, true);
         furi_string_cat_printf(parsed_data, "\n");
-    } else if(event->transport_type == TRAIN) {
+    } 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_transition_type(event->transition));
+                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_train_line(event->station_group_id),
-                get_transition_type(event->transition));
+                get_navigo_sncf_train_line(event->station_group_id),
+                get_intercode_string_transition_type(event->transition));
         }
         furi_string_cat_printf(
             parsed_data,
             "Transporter: %s\n",
             get_navigo_service_provider(event->service_provider));
-        if(event->service_provider == 2) {
-            furi_string_cat_printf(
-                parsed_data,
-                "Station: %s\n",
-                get_navigo_train_station(event->station_group_id, event->station_id));
-        } else if(event->service_provider == 3) {
-            furi_string_cat_printf(
-                parsed_data,
-                "Station: %s\n",
-                get_navigo_metro_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);
-        } */
+        furi_string_cat_printf(parsed_data, "Station: %s\n", station);
         if(event->location_gate_available) {
             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);
@@ -430,13 +453,12 @@ void show_navigo_event_info(
             parsed_data,
             "%s - %s\n",
             get_navigo_transport_type(event->transport_type),
-            get_transition_type(event->transition));
+            get_intercode_string_transition_type(event->transition));
         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);
+        furi_string_cat_printf(parsed_data, "Station: %s\n", station);
         if(event->location_gate_available) {
             furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
         }
@@ -459,6 +481,112 @@ void show_navigo_event_info(
         locale_format_datetime_cat(parsed_data, &event->date, true);
         furi_string_cat_printf(parsed_data, "\n");
     }
+
+    free(station);
+    free(sector);
+}
+
+void show_navigo_special_event_info(NavigoCardSpecialEvent* event, FuriString* parsed_data) {
+    char* station = get_navigo_station(
+        event->station_group_id, event->station_id, event->station_sub_id, event->service_provider);
+    char* sector = get_navigo_station(event->station_group_id, 0, 0, event->service_provider);
+
+    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", station, sector);
+        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", station);
+        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: %s\n", station);
+        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");
+    }
+
+    free(station);
+    free(sector);
 }
 
 void show_navigo_contract_info(NavigoCardContract* contract, FuriString* parsed_data) {

+ 20 - 12
api/calypso/transit/navigo.h

@@ -10,11 +10,15 @@
 
 const char* get_navigo_type(int type);
 
-const char* get_navigo_metro_station(int station_group_id, int station_id);
+char* get_navigo_station(
+    int station_group_id,
+    int station_id,
+    int station_sub_id,
+    int service_provider);
 
-const char* get_navigo_train_line(int station_group_id);
+const char* get_navigo_sncf_train_line(int station_group_id);
 
-const char* get_navigo_train_station(int station_group_id, int station_id);
+const char* get_navigo_sncf_station(int station_group_id, int station_id);
 
 const char* get_navigo_tram_line(int route_number);
 
@@ -23,19 +27,12 @@ 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);
 
-typedef enum {
-    BUS_URBAIN = 1,
-    BUS_INTERURBAIN = 2,
-    METRO = 3,
-    TRAM = 4,
-    TRAIN = 5,
-    PARKING = 8
-} NAVIGO_TRANSPORT_TYPE;
-
 typedef enum {
     NAVIGO_EASY = 0,
     NAVIGO_DECOUVERTE = 1,
@@ -44,4 +41,15 @@ typedef enum {
     IMAGINE_R = 14
 } NAVIGO_CARD_STATUS;
 
+typedef enum {
+    NAVIGO_PROVIDER_SNCF = 2,
+    NAVIGO_PROVIDER_RATP = 3,
+    NAVIGO_PROVIDER_IDFM = 4,
+    NAVIGO_PROVIDER_ORA = 8,
+    NAVIGO_PROVIDER_VEOLIA_CSO = 115,
+    NAVIGO_PROVIDER_VEOLIA_RBUS = 116,
+    NAVIGO_PROVIDER_PHEBUS = 156,
+    NAVIGO_PROVIDER_RATP_VEOLIA_NANTERRE = 175
+} NAVIGO_SERVICE_PROVIDER;
+
 #endif // NAVIGO_H

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

@@ -10,6 +10,7 @@ typedef struct {
     int service_provider;
     int station_group_id;
     int station_id;
+    int station_sub_id;
     int location_gate;
     bool location_gate_available;
     int device;
@@ -27,6 +28,21 @@ 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 station_sub_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 +88,7 @@ typedef struct {
     NavigoCardHolder holder;
     NavigoCardContract contracts[4];
     NavigoCardEvent events[3];
+    NavigoCardSpecialEvent special_events[3];
 } NavigoCardData;
 
 #endif // NAVIGO_I_H

+ 134 - 134
api/calypso/transit/navigo_lists.h

@@ -1,7 +1,7 @@
 #ifndef NAVIGO_LISTS_H
 #define NAVIGO_LISTS_H
 
-static const char* NAVIGO_METRO_STATION_LIST[32][16] =
+static const char* NAVIGO_RATP_LOCATION_LIST[32][16] =
     {[1] =
          {[0] = "Cite",
           [1] = "Saint-Michel",
@@ -323,7 +323,8 @@ static const char* NAVIGO_METRO_STATION_LIST[32][16] =
           [6] = "Le Kremlin-Bicetre",
           [7] = "Villejuif - Leo Lagrange",
           [8] = "Villejuif - Paul Vaillant-Couturier",
-          [9] = "Villejuif - Louis Aragon"},
+          [9] = "Villejuif - Louis Aragon",
+          [11] = "Villejuif - Institut Gustave Roussy"},
      [27] =
          {[0] = "Vanves",
           [2] = "Porte de Vanves",
@@ -373,7 +374,7 @@ static const char* NAVIGO_METRO_STATION_LIST[32][16] =
          [13] = "Place de Clichy",
          [14] = "La Fourche"}};
 
-static const char* NAVIGO_TRAIN_LINES_LIST[77] = {
+static const char* NAVIGO_SNCF_TRAIN_LINES_LIST[77] = {
     [1] = "RER B",         [3] = "RER B",         [6] = "RER A",         [14] = "RER B",
     [15] = "RER B",        [16] = "RER A",        [17] = "RER A",        [18] = "RER B",
     [20] = "Transilien P", [21] = "Transilien P", [22] = "T4",           [23] = "Transilien P",
@@ -386,226 +387,225 @@ static const char* NAVIGO_TRAIN_LINES_LIST[77] = {
     [64] = "RER C",        [65] = "Transilien V", [70] = "RER B",        [72] = "Transilien J",
     [73] = "Transilien J", [75] = "RER C",        [76] = "RER C"};
 
-static const char* NAVIGO_TRAIN_STATION_LIST[77][19] = {
-    [1] = {[0] = "Châtelet-Les Halles", [1] = "Châtelet-Les Halles", [7] = "Luxembourg"},
+static const char* NAVIGO_SNCF_LOCATION_LIST[77][19] = {
+    [1] = {[0] = "Chatelet-Les Halles", [1] = "Chatelet-Les Halles", [7] = "Luxembourg"},
     [3] = {[0] = "Saint-Michel Notre-Dame"},
     [6] = {[0] = "Auber", [6] = "Auber"},
     [14] = {[4] = "Cite Universitaire"},
     [15] = {[12] = "Port Royal"},
     [16] =
         {[1] = "Nation",
-         [2] = "Fontenay-sous-Bois | Vincennes",
-         [3] = "Joinville-le-Pont | Nogent-sur-Marne",
+         [2] = "Fontenay-sous-Bois|Vincennes",
+         [3] = "Joinville-le-Pont|Nogent-sur-Marne",
          [4] = "Saint-Maur Creteil",
          [5] = "Le Parc de Saint-Maur",
          [6] = "Champigny",
          [7] = "La Varenne-Chennevieres",
-         [8] = "Boissy-Saint-Leger | Sucy Bonneuil"},
+         [8] = "Boissy-Saint-Leger|Sucy Bonneuil"},
     [17] =
         {[1] = "Charles de Gaulle-Etoile",
          [4] = "La Defense (Grande Arche)",
          [5] = "Nanterre-Ville",
          [6] = "Rueil-Malmaison",
          [8] = "Chatou-Croissy",
-         [9] = "Le Vesinet-Centre | Le Vesinet-Le Pecq | Saint-Germain-en-Laye"},
+         [9] = "Le Vesinet-Centre|Le Vesinet-Le Pecq|Saint-Germain-en-Laye"},
     [18] =
         {[0] = "Denfert-Rochereau",
          [1] = "Gentilly",
-         [2] = "Arcueil-Cachan | Laplace",
-         [3] = "Bagneux | Bourg-la-Reine",
-         [4] = "La Croix-de-Berny | Parc de Sceaux",
-         [5] = "Antony | Fontaine-Michalon | Les Baconnets",
-         [6] = "Massy-Palaiseau | Massy-Verrieres",
-         [7] = "Palaiseau Villebon | Palaiseau",
+         [2] = "Arcueil-Cachan|Laplace",
+         [3] = "Bagneux|Bourg-la-Reine",
+         [4] = "La Croix-de-Berny|Parc de Sceaux",
+         [5] = "Antony|Fontaine-Michalon|Les Baconnets",
+         [6] = "Massy-Palaiseau|Massy-Verrieres",
+         [7] = "Palaiseau Villebon|Palaiseau",
          [8] = "Lozere",
-         [9] = "Le Guichet | Orsay-Ville",
+         [9] = "Le Guichet|Orsay-Ville",
          [10] =
-             "Bures-sur-Yvette | Courcelle-sur-Yvette | Gif-sur-Yvette | La Hacquiniere | Saint-Remy-les-Chevreuse"},
+             "Bures-sur-Yvette|Courcelle-sur-Yvette|Gif-sur-Yvette|La Hacquiniere|Saint-Remy-les-Chevreuse"},
     [20] =
         {[1] = "Gare de l'Est",
          [4] = "Pantin",
          [5] = "Noisy-le-Sec",
          [6] = "Bondy",
-         [7] = "Gagny | Le Raincy Villemomble Montfermeil",
-         [9] = "Chelles Gournay | Le Chenay Gagny",
+         [7] = "Gagny|Le Raincy Villemomble Montfermeil",
+         [9] = "Chelles Gournay|Le Chenay Gagny",
          [10] = "Vaires Torcy",
          [11] = "Lagny-Thorigny",
          [13] = "Esbly",
          [14] = "Meaux",
-         [15] = "Changis-Saint-Jean | Isles-Armentieres Congis | Lizy-sur-Ourcq | Trilport",
-         [16] = "Crouy-sur-Ourcq | La Ferte-sous-Jouarre | Nanteuil Saacy"},
+         [15] = "Changis-Saint-Jean|Isles-Armentieres Congis|Lizy-sur-Ourcq|Trilport",
+         [16] = "Crouy-sur-Ourcq|La Ferte-sous-Jouarre|Nanteuil Saacy"},
     [21] =
-        {[5] = "Rosny-Bois-Perrier | Rosny-sous-Bois | Val de Fontenay",
+        {[5] = "Rosny-Bois-Perrier|Rosny-sous-Bois|Val de Fontenay",
          [6] = "Nogent Le-Perreux",
          [7] = "Les Boullereaux Champigny",
          [8] = "Villiers-sur-Marne Plessis-Trevise",
          [9] = "Les Yvris Noisy-le-Grand",
-         [10] = "Emerainville Pontault-Combault | Roissy-en-Brie",
+         [10] = "Emerainville Pontault-Combault|Roissy-en-Brie",
          [11] = "Ozoir-la-Ferriere",
-         [12] = "Gretz-Armainvilliers | Tournan",
+         [12] = "Gretz-Armainvilliers|Tournan",
          [15] =
-             "Courquetaine | Faremoutiers Pommeuse | Guerard La-Celle-sur-Morin | Liverdy en Brie | Marles-en-Brie | Mormant | Mortcerf | Mouroux | Ozouer le voulgis | Verneuil-l'Etang | Villepatour - Presles | Yebles - Guignes | Yebles",
+             "Courquetaine|Faremoutiers Pommeuse|Guerard La-Celle-sur-Morin|Liverdy en Brie|Marles-en-Brie|Mormant|Mortcerf|Mouroux|Ozouer le voulgis|Verneuil-l'Etang|Villepatour - Presles|Yebles - Guignes|Yebles",
          [16] =
-             "Chailly Boissy-le-Châtel | Chauffry | Coulommiers | Jouy-sur-Morin Le-Marais | Nangis | Saint-Remy-la-Vanne | Saint-Simeon",
+             "Chailly Boissy-le-Châtel|Chauffry|Coulommiers|Jouy-sur-Morin Le-Marais|Nangis|Saint-Remy-la-Vanne|Saint-Simeon",
          [17] =
-             "Champbenoist-Poigny | La Ferte-Gaucher | Longueville | Provins | Sainte-Colombe-Septveilles",
-         [18] = "Flamboin | Meilleray | Villiers St Georges"},
+             "Champbenoist-Poigny|La Ferte-Gaucher|Longueville|Provins|Sainte-Colombe-Septveilles",
+         [18] = "Flamboin|Meilleray|Villiers St Georges"},
     [22] =
         {[7] =
-             "Allee de la Tour-Rendez-Vous | La Remise-a-Jorelle | Les Coquetiers | Les Pavillons-sous-Bois",
+             "Allee de la Tour-Rendez-Vous|La Remise-a-Jorelle|Les Coquetiers|Les Pavillons-sous-Bois",
          [8] = "Gargan",
-         [9] = "Freinville Sevran | L'Abbaye"},
+         [9] = "Freinville Sevran|L'Abbaye"},
     [23] =
-        {[13] = "Couilly Saint-Germain Quincy | Les Champs-Forts | Montry Conde",
-         [14] = "Crecy-en-Brie La Chapelle | Villiers-Montbarbin"},
+        {[13] = "Couilly Saint-Germain Quincy|Les Champs-Forts|Montry Conde",
+         [14] = "Crecy-en-Brie La Chapelle|Villiers-Montbarbin"},
     [26] =
         {[5] = "Val de Fontenay",
-         [6] = "Bry-sur-Marne | Neuilly-Plaisance",
+         [6] = "Bry-sur-Marne|Neuilly-Plaisance",
          [7] = "Noisy-le-Grand (Mont d'Est)",
          [8] = "Noisy-Champs",
-         [10] = "Lognes | Noisiel | Torcy",
+         [10] = "Lognes|Noisiel|Torcy",
          [11] = "Bussy-Saint-Georges",
          [12] = "Val d'europe",
          [13] = "Marne-la-Vallee Chessy"},
-    [28] = {[4] = "Fontenay-aux-Roses | Robinson | Sceaux"},
+    [28] = {[4] = "Fontenay-aux-Roses|Robinson|Sceaux"},
     [30] =
         {[1] = "Gare Saint-Lazare",
          [3] = "Pont Cardinet",
-         [4] =
-             "Asnieres | Becon-les-Bruyeres | Clichy Levallois | Courbevoie | La Defense (Grande Arche)",
-         [5] = "Puteaux | Suresnes Mont-Valerien",
-         [7] = "Garches Marne-la-Coquette | Le Val d'Or | Saint-Cloud",
+         [4] = "Asnieres|Becon-les-Bruyeres|Clichy Levallois|Courbevoie|La Defense (Grande Arche)",
+         [5] = "Puteaux|Suresnes Mont-Valerien",
+         [7] = "Garches Marne-la-Coquette|Le Val d'Or|Saint-Cloud",
          [8] = "Vaucresson",
-         [9] = "Bougival | La Celle-Saint-Cloud | Louveciennes | Marly-le-Roi",
-         [10] = "L'Etang-la-Ville | Saint-Nom-la-Breteche Foret de Marly"},
+         [9] = "Bougival|La Celle-Saint-Cloud|Louveciennes|Marly-le-Roi",
+         [10] = "L'Etang-la-Ville|Saint-Nom-la-Breteche Foret de Marly"},
     [31] =
-        {[7] = "Chaville-Rive Droite | Sevres Ville-d'Avray | Viroflay-Rive Droite",
-         [8] = "Montreuil | Versailles-Rive Droite"},
+        {[7] = "Chaville-Rive Droite|Sevres Ville-d'Avray|Viroflay-Rive Droite",
+         [8] = "Montreuil|Versailles-Rive Droite"},
     [32] =
-        {[5] = "La Garenne-Colombes | Les Vallees | Nanterre-Universite",
-         [7] = "Houilles Carrieres-sur-Seine | Sartrouville",
+        {[5] = "La Garenne-Colombes|Les Vallees|Nanterre-Universite",
+         [7] = "Houilles Carrieres-sur-Seine|Sartrouville",
          [9] = "Maisons-Laffitte",
          [10] = "Poissy",
          [11] = "Villennes-sur-Seine",
-         [12] = "Les Clairieres de Verneuil | Vernouillet Verneuil",
-         [13] = "Aubergenville-Elisabethville | Les Mureaux",
+         [12] = "Les Clairieres de Verneuil|Vernouillet Verneuil",
+         [13] = "Aubergenville-Elisabethville|Les Mureaux",
          [14] = "Epone Mezieres",
-         [16] = "Bonnieres | Mantes-Station | Mantes-la-Jolie | Port-Villez | Rosny-sur-Seine"},
+         [16] = "Bonnieres|Mantes-Station|Mantes-la-Jolie|Port-Villez|Rosny-sur-Seine"},
     [33] =
-        {[10] = "Acheres-Grand-Cormier | Acheres-Ville",
-         [11] = "Cergy-Prefecture | Neuville-Universite",
-         [12] = "Cergy-Saint-Christophe | Cergy-le-Haut"},
+        {[10] = "Acheres-Grand-Cormier|Acheres-Ville",
+         [11] = "Cergy-Prefecture|Neuville-Universite",
+         [12] = "Cergy-Saint-Christophe|Cergy-le-Haut"},
     [35] =
         {[4] = "Bois-Colombes",
-         [5] = "Colombes | Le Stade",
-         [6] = "Argenteuil | Argenteuil",
-         [8] = "Cormeilles-en-Parisis | Val d'Argenteuil | Val d'Argenteuil",
-         [9] = "Herblay | La Frette Montigny",
-         [10] = "Conflans-Fin d'Oise | Conflans-Sainte-Honorine",
-         [11] = "Andresy | Chanteloup-les-Vignes | Maurecourt",
-         [12] = "Triel-sur-Seine | Vaux-sur-Seine",
-         [13] = "Meulan Hadricourt | Thun-le-Paradis",
-         [14] = "Gargenville | Juziers",
-         [15] = "Issou Porcheville | Limay",
-         [16] = "Breval | Menerville"},
+         [5] = "Colombes|Le Stade",
+         [6] = "Argenteuil|Argenteuil",
+         [8] = "Cormeilles-en-Parisis|Val d'Argenteuil|Val d'Argenteuil",
+         [9] = "Herblay|La Frette Montigny",
+         [10] = "Conflans-Fin d'Oise|Conflans-Sainte-Honorine",
+         [11] = "Andresy|Chanteloup-les-Vignes|Maurecourt",
+         [12] = "Triel-sur-Seine|Vaux-sur-Seine",
+         [13] = "Meulan Hadricourt|Thun-le-Paradis",
+         [14] = "Gargenville|Juziers",
+         [15] = "Issou Porcheville|Limay",
+         [16] = "Breval|Menerville"},
     [40] =
         {[1] = "Gare de Lyon",
-         [5] = "Le Vert de Maisons | Maisons-Alfort Alfortville",
+         [5] = "Le Vert de Maisons|Maisons-Alfort Alfortville",
          [6] = "Villeneuve-Prairie",
          [7] = "Villeneuve-Triage",
          [8] = "Villeneuve-Saint-Georges",
-         [9] = "Juvisy | Vigneux-sur-Seine",
-         [10] = "Ris-Orangis | Viry-Châtillon",
-         [11] = "Evry Val de Seine | Grand-Bourg",
-         [12] = "Corbeil-Essonnes | Mennecy | Moulin-Galant",
-         [13] = "Ballancourt | Fontenay le Vicomte",
+         [9] = "Juvisy|Vigneux-sur-Seine",
+         [10] = "Ris-Orangis|Viry-Châtillon",
+         [11] = "Evry Val de Seine|Grand-Bourg",
+         [12] = "Corbeil-Essonnes|Mennecy|Moulin-Galant",
+         [13] = "Ballancourt|Fontenay le Vicomte",
          [14] = "La Ferte-Alais",
-         [16] = "Boutigny | Maisse",
-         [17] = "Boigneville | Buno-Gironville"},
+         [16] = "Boutigny|Maisse",
+         [17] = "Boigneville|Buno-Gironville"},
     [41] =
-        {[0] = "Musee d'Orsay | Saint-Michel Notre-Dame",
+        {[0] = "Musee d'Orsay|Saint-Michel Notre-Dame",
          [1] = "Gare d'Austerlitz",
          [2] = "Bibliotheque-Francois Mitterrand",
-         [4] = "Ivry-sur-Seine | Vitry-sur-Seine",
-         [5] = "Choisy-le-Roi | Les Ardoines",
+         [4] = "Ivry-sur-Seine|Vitry-sur-Seine",
+         [5] = "Choisy-le-Roi|Les Ardoines",
          [7] = "Villeneuve-le-Roi",
          [8] = "Ablon",
          [9] = "Athis-Mons"},
     [42] =
-        {[9] = "Epinay-sur-Orge | Savigny-sur-Orge",
+        {[9] = "Epinay-sur-Orge|Savigny-sur-Orge",
          [10] = "Sainte-Genevieve-des-Bois",
          [11] = "Saint-Michel-sur-Orge",
-         [12] = "Bretigny-sur-Orge | Marolles-en-Hurepoix",
-         [13] = "Bouray | Lardy",
-         [14] = "Chamarande | Etampes | Etrechy",
+         [12] = "Bretigny-sur-Orge|Marolles-en-Hurepoix",
+         [13] = "Bouray|Lardy",
+         [14] = "Chamarande|Etampes|Etrechy",
          [16] = "Saint-Martin d'Etampes",
          [17] = "Guillerval"},
     [43] =
-        {[9] = "Montgeron Crosne | Yerres",
+        {[9] = "Montgeron Crosne|Yerres",
          [10] = "Brunoy",
-         [11] = "Boussy-Saint-Antoine | Combs-la-Ville Quincy",
+         [11] = "Boussy-Saint-Antoine|Combs-la-Ville Quincy",
          [12] = "Lieusaint Moissy",
-         [13] = "Cesson | Savigny-le-Temple Nandy",
-         [15] = "Le Mee | Melun",
-         [16] = "Chartrettes | Fontaine-le-Port | Livry-sur-Seine",
+         [13] = "Cesson|Savigny-le-Temple Nandy",
+         [15] = "Le Mee|Melun",
+         [16] = "Chartrettes|Fontaine-le-Port|Livry-sur-Seine",
          [17] =
-             "Champagne-sur-Seine | Hericy | La Grande Paroisse | Vernou-sur-Seine | Vulaines-sur-Seine Samoreau"},
+             "Champagne-sur-Seine|Hericy|La Grande Paroisse|Vernou-sur-Seine|Vulaines-sur-Seine Samoreau"},
     [44] =
-        {[12] = "Essonnes-Robinson | Villabe",
-         [13] = "Coudray-Montceaux | Le Plessis-Chenet-IBM | Saint-Fargeau",
-         [14] = "Boissise-le-Roi | Ponthierry Pringy",
+        {[12] = "Essonnes-Robinson|Villabe",
+         [13] = "Coudray-Montceaux|Le Plessis-Chenet-IBM|Saint-Fargeau",
+         [14] = "Boissise-le-Roi|Ponthierry Pringy",
          [15] = "Vosves",
          [16] = "Bois-le-Roi",
          [17] =
-             "Bagneaux-sur-Loing | Bourron-Marlotte Grez | Fontainebleau-Avon | Montereau | Montigny-sur-Loing | Moret Veneux-les-Sablons | Nemours Saint-Pierre | Saint-Mammes | Souppes | Thomery"},
+             "Bagneaux-sur-Loing|Bourron-Marlotte Grez|Fontainebleau-Avon|Montereau|Montigny-sur-Loing|Moret Veneux-les-Sablons|Nemours Saint-Pierre|Saint-Mammes|Souppes|Thomery"},
     [45] =
         {[10] = "Grigny-Centre",
-         [11] = "Evry Courcouronnes | Orangis Bois de l'Epine",
+         [11] = "Evry Courcouronnes|Orangis Bois de l'Epine",
          [12] = "Le Bras-de-Fer - Evry Genopole"},
     [50] =
         {[0] = "Haussmann-Saint-Lazare",
-         [1] = "Gare du Nord | Magenta | Paris-Nord",
-         [5] = "Epinay-Villetaneuse | Saint-Denis | Sevres-Rive Gauche",
+         [1] = "Gare du Nord|Magenta|Paris-Nord",
+         [5] = "Epinay-Villetaneuse|Saint-Denis|Sevres-Rive Gauche",
          [6] = "La Barre-Ormesson",
-         [7] = "Champ de Courses d'Enghien | Enghien-les-Bains",
-         [8] = "Ermont-Eaubonne | Ermont-Halte | Gros-Noyer Saint-Prix",
-         [9] = "Saint-Leu-La-Foret | Taverny | Vaucelles",
-         [10] = "Bessancourt | Frepillon | Mery",
-         [11] = "Meriel | Valmondois",
-         [12] = "Bruyeres-sur-Oise | Champagne-sur-Oise | L'Isle-Adam Parmain | Persan Beaumont"},
+         [7] = "Champ de Courses d'Enghien|Enghien-les-Bains",
+         [8] = "Ermont-Eaubonne|Ermont-Halte|Gros-Noyer Saint-Prix",
+         [9] = "Saint-Leu-La-Foret|Taverny|Vaucelles",
+         [10] = "Bessancourt|Frepillon|Mery",
+         [11] = "Meriel|Valmondois",
+         [12] = "Bruyeres-sur-Oise|Champagne-sur-Oise|L'Isle-Adam Parmain|Persan Beaumont"},
     [51] =
-        {[4] = "La Courneuve-Aubervilliers | La Plaine-Stade de France",
+        {[4] = "La Courneuve-Aubervilliers|La Plaine-Stade de France",
          [5] = "Le Bourget",
-         [7] = "Blanc-Mesnil | Drancy",
+         [7] = "Blanc-Mesnil|Drancy",
          [8] = "Aulnay-sous-Bois",
-         [9] = "Sevran Livry | Vert-Galant",
+         [9] = "Sevran Livry|Vert-Galant",
          [10] = "Villeparisis",
-         [11] = "Compans | Mitry-Claye",
-         [12] = "Dammartin Juilly Saint-Mard | Thieux Nantouillet"},
+         [11] = "Compans|Mitry-Claye",
+         [12] = "Dammartin Juilly Saint-Mard|Thieux Nantouillet"},
     [52] =
         {[5] = "Stade de France-Saint-Denis",
          [6] = "Pierrefitte Stains",
          [7] = "Garges-Sarcelles",
          [8] = "Villiers-le-Bel (Gonesse - Arnouville)",
-         [10] = "Goussainville | Les Noues | Louvres",
-         [11] = "La Borne-Blanche | Survilliers-Fosses"},
+         [10] = "Goussainville|Les Noues|Louvres",
+         [11] = "La Borne-Blanche|Survilliers-Fosses"},
     [53] =
         {[6] = "Deuil Montmagny",
          [7] = "Groslay",
          [8] = "Sarcelles Saint-Brice",
-         [9] = "Domont | Ecouen Ezanville",
-         [10] = "Bouffemont Moisselles | Montsoult Maffliers",
-         [11] = "Belloy-Saint-Martin | Luzarches | Seugy | Viarmes | Villaines"},
+         [9] = "Domont|Ecouen Ezanville",
+         [10] = "Bouffemont Moisselles|Montsoult Maffliers",
+         [11] = "Belloy-Saint-Martin|Luzarches|Seugy|Viarmes|Villaines"},
     [54] =
         {[8] = "Cernay",
-         [9] = "Franconville Plessis-Bouchard | Montigny-Beauchamp",
+         [9] = "Franconville Plessis-Bouchard|Montigny-Beauchamp",
          [10] = "Pierrelaye",
-         [11] = "Pontoise | Saint-Ouen-l'Aumone-Liesse",
-         [12] = "Boissy-l'Aillerie | Osny",
-         [15] = "Chars | Montgeroult Courcelles | Santeuil Le Perchay | Us"},
+         [11] = "Pontoise|Saint-Ouen-l'Aumone-Liesse",
+         [12] = "Boissy-l'Aillerie|Osny",
+         [15] = "Chars|Montgeroult Courcelles|Santeuil Le Perchay|Us"},
     [55] =
         {[0] =
-             "Avenue Foch | Avenue Henri-Martin | Boulainvilliers | Kennedy Radio-France | Neuilly-Porte Maillot (Palais des congres)",
+             "Avenue Foch|Avenue Henri-Martin|Boulainvilliers|Kennedy Radio-France|Neuilly-Porte Maillot (Palais des congres)",
          [1] = "Pereire-Levallois",
          [2] = "Porte de Clichy",
          [3] = "Saint-Ouen",
@@ -613,58 +613,58 @@ static const char* NAVIGO_TRAIN_STATION_LIST[77][19] = {
          [5] = "Gennevilliers",
          [6] = "Epinay-sur-Seine",
          [7] = "Saint-Gratien"},
-    [56] = {[11] = "Auvers-sur-Oise | Chaponval | Epluches | Pont Petit"},
+    [56] = {[11] = "Auvers-sur-Oise|Chaponval|Epluches|Pont Petit"},
     [57] = {[11] = "Presles Courcelles", [12] = "Nointel Mours"},
     [60] =
         {[1] = "Gare Montparnasse",
-         [4] = "Clamart | Vanves Malakoff",
-         [5] = "Bellevue | Bievres | Meudon",
-         [6] = "Chaville-Rive Gauche | Chaville-Velizy | Viroflay-Rive Gauche",
+         [4] = "Clamart|Vanves Malakoff",
+         [5] = "Bellevue|Bievres|Meudon",
+         [6] = "Chaville-Rive Gauche|Chaville-Velizy|Viroflay-Rive Gauche",
          [7] = "Versailles-Chantiers",
          [10] = "Saint-Cyr",
-         [11] = "Saint-Quentin-en-Yvelines - Montigny le Bretonneux | Trappes",
-         [12] = "Coignieres | La Verriere",
+         [11] = "Saint-Quentin-en-Yvelines - Montigny le Bretonneux|Trappes",
+         [12] = "Coignieres|La Verriere",
          [13] = "Les Essarts-le-Roi",
-         [14] = "Le Perray | Rambouillet",
+         [14] = "Le Perray|Rambouillet",
          [15] = "Gazeran"},
     [61] =
         {[10] = "Fontenay-le-Fleury",
          [11] = "Villepreux Les-Clayes",
-         [12] = "Plaisir Grignon | Plaisir Les-Clayes",
-         [13] = "Beynes | Mareil-sur-Mauldre | Maule | Nezel Aulnay",
+         [12] = "Plaisir Grignon|Plaisir Les-Clayes",
+         [13] = "Beynes|Mareil-sur-Mauldre|Maule|Nezel Aulnay",
          [15] =
-             "Garancieres La-Queue | Montfort-l'Amaury Mere | Orgerus Behoust | Tacoigneres Richebourg | Villiers Neauphle Pontchartrain",
+             "Garancieres La-Queue|Montfort-l'Amaury Mere|Orgerus Behoust|Tacoigneres Richebourg|Villiers Neauphle Pontchartrain",
          [16] = "Houdan"},
-    [63] = {[7] = "Porchefontaine | Versailles-Rive Gauche"},
+    [63] = {[7] = "Porchefontaine|Versailles-Rive Gauche"},
     [64] =
-        {[0] = "Invalides | Pont de l'alma",
+        {[0] = "Invalides|Pont de l'alma",
          [1] = "Champ de Mars-Tour Eiffel",
          [2] = "Javel",
-         [3] = "Boulevard Victor - Pont du Garigliano | Issy-Val de Seine | Issy",
+         [3] = "Boulevard Victor - Pont du Garigliano|Issy-Val de Seine|Issy",
          [5] = "Meudon-Val-Fleury"},
     [65] =
-        {[8] = "Jouy-en-Josas | Petit-Jouy-les-Loges",
+        {[8] = "Jouy-en-Josas|Petit-Jouy-les-Loges",
          [9] = "Vauboyen",
          [10] = "Igny",
          [11] = "Massy-Palaiseau",
          [12] = "Longjumeau",
          [13] = "Chilly-Mazarin",
-         [14] = "Gravigny-Balizy | Petit-Vaux"},
+         [14] = "Gravigny-Balizy|Petit-Vaux"},
     [70] =
-        {[9] = "Parc des Expositions | Sevran-Beaudottes | Villepinte",
+        {[9] = "Parc des Expositions|Sevran-Beaudottes|Villepinte",
          [10] = "Aeroport Charles de Gaulle"},
     [72] = {[7] = "Sannois"},
-    [73] = {[11] = "Eragny Neuville | Saint-Ouen-l'Aumone (Eglise)"},
+    [73] = {[11] = "Eragny Neuville|Saint-Ouen-l'Aumone (Eglise)"},
     [75] =
-        {[7] = "Les Saules | Orly-Ville",
-         [9] = "Pont de Rungis Aeroport d'Orly | Rungis-La Fraternelle",
+        {[7] = "Les Saules|Orly-Ville",
+         [9] = "Pont de Rungis Aeroport d'Orly|Rungis-La Fraternelle",
          [10] = "Chemin d'Antony",
-         [12] = "Massy-Verrieres | Arpajon"},
+         [12] = "Massy-Verrieres|Arpajon"},
     [76] =
-        {[12] = "Egly | La Norville Saint-Germain-les-Arpajon",
-         [13] = "Breuillet Bruyeres-le-Châtel | Breuillet-Village | Saint-Cheron",
+        {[12] = "Egly|La Norville Saint-Germain-les-Arpajon",
+         [13] = "Breuillet Bruyeres-le-Châtel|Breuillet-Village|Saint-Cheron",
          [14] = "Sermaise",
-         [15] = "Dourdan | Dourdan-la-Foret"},
+         [15] = "Dourdan|Dourdan-la-Foret"},
 };
 
 #endif

+ 0 - 1
api/calypso/transit/opus.c

@@ -5,7 +5,6 @@
 const char* get_opus_service_provider(int provider) {
     switch(provider) {
     case 0x01:
-        return "STM";
     case 0x02:
         return "STM";
     case 0x03:

+ 238 - 25
scenes/metroflip_scene_calypso.c

@@ -185,6 +185,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;
+        }
+        }
     }
 }
 
@@ -198,7 +213,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 {
@@ -224,6 +239,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;
             }
@@ -275,7 +305,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;
             }
@@ -285,6 +315,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;
@@ -455,7 +506,7 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                     card->navigo->environment.country_num = country_num;
                     card->navigo->environment.network_num = network_num;
 
-                    CalypsoApp* IntercodeEnvHolderStructure = get_intercode_env_holder_structure();
+                    CalypsoApp* IntercodeEnvHolderStructure = get_intercode_structure_env_holder();
 
                     // EnvApplicationVersionNumber
                     const char* env_key = "EnvApplicationVersionNumber";
@@ -520,7 +571,7 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                     }
 
                     // Prepare calypso structure
-                    CalypsoApp* IntercodeContractStructure = get_intercode_contract_structure();
+                    CalypsoApp* IntercodeContractStructure = get_intercode_structure_contract();
                     if(!IntercodeContractStructure) {
                         FURI_LOG_E(TAG, "Failed to load Intercode Contract structure");
                         break;
@@ -825,7 +876,7 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                     }
 
                     // Load the calypso structure for events
-                    CalypsoApp* IntercodeEventStructure = get_intercode_event_structure();
+                    CalypsoApp* IntercodeEventStructure = get_intercode_structure_event();
                     if(!IntercodeEventStructure) {
                         FURI_LOG_E(TAG, "Failed to load Intercode Event structure");
                         break;
@@ -855,18 +906,6 @@ static NfcCommand metroflip_scene_calypso_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(
@@ -909,6 +948,7 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                                 bit_slice_to_dec(event_bit_representation, start, end);
                             card->navigo->events[i - 1].station_group_id = decimal_value >> 9;
                             card->navigo->events[i - 1].station_id = (decimal_value >> 4) & 31;
+                            card->navigo->events[i - 1].station_sub_id = decimal_value & 15;
                         }
 
                         // 9. EventLocationGate
@@ -997,6 +1037,9 @@ static NfcCommand metroflip_scene_calypso_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
@@ -1026,6 +1069,174 @@ static NfcCommand metroflip_scene_calypso_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;
+                            card->navigo->special_events[i - 1].station_sub_id = decimal_value &
+                                                                                 15;
+                        }
+
+                        // 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;
@@ -1334,29 +1545,31 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                             card->opus->events[i - 1].used_contract =
                                 bit_slice_to_dec(event_bit_representation, start, end);
                             card->opus->events[i - 1].used_contract_available = true;
+                            if(card->opus->events[i - 1].used_contract > 0) {
+                                card->events_count++;
+                            }
                         }
 
-                        // EventDate + EventTime
-                        event_key = "EventDate";
+                        // EventDateStamp
+                        event_key = "EventDateStamp";
                         int positionOffset = get_calypso_node_offset(
                             event_bit_representation, event_key, OpusEventStructure);
                         int start = positionOffset,
                             end = positionOffset +
                                   get_calypso_node_size(event_key, OpusEventStructure) - 1;
-                        uint64_t date_timestamp =
-                            (bit_slice_to_dec(event_bit_representation, start, end) +
-                             (float)epoch) +
-                            3600;
+                        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->opus->events[i - 1].date);
 
-                        event_key = "EventTime";
+                        // EventTimeStamp
+                        event_key = "EventTimeStamp";
                         positionOffset = get_calypso_node_offset(
                             event_bit_representation, event_key, OpusEventStructure);
                         start = positionOffset,
                         end = positionOffset +
                               get_calypso_node_size(event_key, OpusEventStructure) - 1;
-                        int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
+                        decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
                         card->opus->events[i - 1].date.hour = (decimal_value * 60) / 3600;
                         card->opus->events[i - 1].date.minute = ((decimal_value * 60) % 3600) / 60;
                         card->opus->events[i - 1].date.second = ((decimal_value * 60) % 3600) % 60;