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

Merge pull request #35 from DocSystem/dev

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

+ 1 - 0
api/calypso/calypso_i.h

@@ -12,6 +12,7 @@ typedef enum {
     CALYPSO_CARD_VIVA,
     CALYPSO_CARD_PASSPASS,
     CALYPSO_CARD_TAM,
+    CALYPSO_CARD_TRANSPOLE,
     CALYPSO_CARD_OURA,
     CALYPSO_CARD_NAVIGO,
     CALYPSO_CARD_KORRIGO,

+ 46 - 0
api/calypso/calypso_util.c

@@ -1,3 +1,4 @@
+#include "metroflip_i.h"
 #include <stdlib.h>
 #include <string.h>
 #include "calypso_util.h"
@@ -50,6 +51,18 @@ CalypsoElement
     return container_element;
 }
 
+CalypsoElement make_calypso_repeater_element(const char* key, int size, CalypsoElement element) {
+    CalypsoElement repeater_element = {};
+
+    repeater_element.type = CALYPSO_ELEMENT_TYPE_REPEATER;
+    repeater_element.repeater = malloc(sizeof(CalypsoRepeaterElement));
+    repeater_element.repeater->size = size;
+    repeater_element.repeater->element = element;
+    strncpy(repeater_element.repeater->key, key, 36);
+
+    return repeater_element;
+}
+
 void free_calypso_element(CalypsoElement* element) {
     if(element->type == CALYPSO_ELEMENT_TYPE_FINAL) {
         free(element->final);
@@ -65,6 +78,9 @@ void free_calypso_element(CalypsoElement* element) {
         }
         free(element->container->elements);
         free(element->container);
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_REPEATER) {
+        free_calypso_element(&element->repeater->element);
+        free(element->repeater);
     }
 }
 
@@ -219,6 +235,26 @@ int get_calypso_subnode_offset(
         }
 
         return count_offset;
+    } else if(elem->type == CALYPSO_ELEMENT_TYPE_REPEATER) {
+        // same as bitmap but instead of a bitmap, we have the count of how many times to repeat the inner element
+        CalypsoRepeaterElement* repeater = elem->repeater;
+
+        char bit_slice[repeater->size + 1];
+        strncpy(bit_slice, binary_string, repeater->size);
+        bit_slice[repeater->size] = '\0';
+
+        int C = bit_slice_to_dec(bit_slice, 0, repeater->size - 1);
+
+        int count_offset = repeater->size;
+        bool f = false;
+        for(int i = 0; i < C; i++) {
+            count_offset += get_calypso_subnode_offset(
+                binary_string + count_offset, key, &repeater->element, &f);
+            if(f) {
+                *found = true;
+                return count_offset;
+            }
+        }
     }
     return 0;
 }
@@ -261,6 +297,14 @@ int get_calypso_subnode_size(const char* key, CalypsoElement* element) {
                 return size;
             }
         }
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_REPEATER) {
+        if(strcmp(element->repeater->key, key) == 0) {
+            return element->repeater->size;
+        }
+        int size = get_calypso_subnode_size(key, &element->repeater->element);
+        if(size != 0) {
+            return size;
+        }
     }
     return 0;
 }
@@ -298,6 +342,8 @@ CALYPSO_CARD_TYPE guess_card_type(int country_num, int network_num) {
             return CALYPSO_CARD_PASSPASS;
         case 64:
             return CALYPSO_CARD_TAM; // Montpellier
+        case 149:
+            return CALYPSO_CARD_TRANSPOLE; // Lille
         case 502:
             return CALYPSO_CARD_OURA;
         case 901:

+ 11 - 0
api/calypso/calypso_util.h

@@ -28,6 +28,7 @@ typedef enum {
 } CalypsoFinalType;
 
 typedef enum {
+    CALYPSO_ELEMENT_TYPE_REPEATER,
     CALYPSO_ELEMENT_TYPE_CONTAINER,
     CALYPSO_ELEMENT_TYPE_BITMAP,
     CALYPSO_ELEMENT_TYPE_FINAL
@@ -36,6 +37,7 @@ typedef enum {
 typedef struct CalypsoFinalElement_t CalypsoFinalElement;
 typedef struct CalypsoBitmapElement_t CalypsoBitmapElement;
 typedef struct CalypsoContainerElement_t CalypsoContainerElement;
+typedef struct CalypsoRepeaterElement_t CalypsoRepeaterElement;
 
 typedef struct {
     CalypsoElementType type;
@@ -43,6 +45,7 @@ typedef struct {
         CalypsoFinalElement* final;
         CalypsoBitmapElement* bitmap;
         CalypsoContainerElement* container;
+        CalypsoRepeaterElement* repeater;
     };
 } CalypsoElement;
 
@@ -65,6 +68,12 @@ struct CalypsoContainerElement_t {
     CalypsoElement* elements;
 };
 
+struct CalypsoRepeaterElement_t {
+    char key[36];
+    int size;
+    CalypsoElement element;
+};
+
 typedef struct {
     CalypsoAppType type;
     CalypsoContainerElement* container;
@@ -80,6 +89,8 @@ CalypsoElement make_calypso_bitmap_element(const char* key, int size, CalypsoEle
 
 CalypsoElement make_calypso_container_element(const char* key, int size, CalypsoElement* elements);
 
+CalypsoElement make_calypso_repeater_element(const char* key, int size, CalypsoElement element);
+
 void free_calypso_structure(CalypsoApp* structure);
 
 int* get_bitmap_positions(const char* binary_string, int* count);

+ 136 - 105
api/calypso/cards/opus.c

@@ -2,32 +2,13 @@
 #include "opus.h"
 
 CalypsoApp* get_opus_contract_structure() {
-    /*
-    En1545FixedInteger(CONTRACT_UNKNOWN_A, 3),
-    En1545Bitmap(
-            En1545FixedInteger(CONTRACT_PROVIDER, 8),
-            En1545FixedInteger(CONTRACT_TARIFF, 16),
-            En1545Bitmap(
-                    En1545FixedInteger.date(CONTRACT_START),
-                    En1545FixedInteger.date(CONTRACT_END)
-            ),
-            En1545Container(
-                    En1545FixedInteger(CONTRACT_UNKNOWN_B, 17),
-                    En1545FixedInteger.date(CONTRACT_SALE),
-                    En1545FixedInteger.timeLocal(CONTRACT_SALE),
-                    En1545FixedHex(CONTRACT_UNKNOWN_C, 36),
-                    En1545FixedInteger(CONTRACT_STATUS, 8),
-                    En1545FixedHex(CONTRACT_UNKNOWN_D, 36)
-            )
-    )
-    */
     CalypsoApp* OpusContractStructure = malloc(sizeof(CalypsoApp));
 
     if(!OpusContractStructure) {
         return NULL;
     }
 
-    int app_elements_count = 2;
+    int app_elements_count = 1;
 
     OpusContractStructure->type = CALYPSO_APP_CONTRACT;
     OpusContractStructure->container = malloc(sizeof(CalypsoContainerElement));
@@ -35,74 +16,91 @@ CalypsoApp* get_opus_contract_structure() {
         malloc(app_elements_count * sizeof(CalypsoElement));
     OpusContractStructure->container->size = app_elements_count;
 
-    OpusContractStructure->container->elements[0] =
-        make_calypso_final_element("ContractUnknownA", 3, "Unknown A", CALYPSO_FINAL_TYPE_NUMBER);
-    OpusContractStructure->container->elements[1] = make_calypso_bitmap_element(
+    OpusContractStructure->container->elements[0] = make_calypso_bitmap_element(
         "Contract",
-        4,
+        7,
         (CalypsoElement[]){
             make_calypso_final_element(
-                "ContractProvider", 8, "Provider", CALYPSO_FINAL_TYPE_UNKNOWN),
-            make_calypso_final_element("ContractTariff", 16, "Tariff", CALYPSO_FINAL_TYPE_TARIFF),
+                "ContractProvider",
+                8,
+                "Acteur ou groupe d’acteurs ayant dèfini et assurant le service pour le contrat",
+                CALYPSO_FINAL_TYPE_SERVICE_PROVIDER),
+            make_calypso_final_element(
+                "ContractTariff", 16, "Code tarif du contrat", CALYPSO_FINAL_TYPE_TARIFF),
             make_calypso_bitmap_element(
-                "ContractDates",
+                "ContractValidityInfoBitmap",
                 2,
                 (CalypsoElement[]){
                     make_calypso_final_element(
-                        "ContractStartDate", 14, "Start date", CALYPSO_FINAL_TYPE_DATE),
+                        "ContractValidityStartDate",
+                        14,
+                        "Date de début de validité du contrat",
+                        CALYPSO_FINAL_TYPE_DATE),
                     make_calypso_final_element(
-                        "ContractEndDate", 14, "End date", CALYPSO_FINAL_TYPE_DATE),
+                        "ContractValidityEndDate",
+                        14,
+                        "Date de fin de validité du contrat",
+                        CALYPSO_FINAL_TYPE_DATE),
                 }),
             make_calypso_bitmap_element(
-                "ContractSaleData",
-                6,
+                "ContractData",
+                9,
                 (CalypsoElement[]){
                     make_calypso_final_element(
-                        "ContractUnknownB", 17, "Unknown B", CALYPSO_FINAL_TYPE_NUMBER),
+                        "ContractDataSaleAgent",
+                        8,
+                        "Acteur ayant effectué la dernière vente sur le contrat",
+                        CALYPSO_FINAL_TYPE_SERVICE_PROVIDER),
+                    make_calypso_final_element(
+                        "ContractDataSaleSecureDevice",
+                        32,
+                        "Numéro du SAM utilisé pour charger le contrat",
+                        CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "ContractDataSaleDate",
+                        14,
+                        "Date de chargement initial du contrat",
+                        CALYPSO_FINAL_TYPE_DATE),
                     make_calypso_final_element(
-                        "ContractSaleDate", 14, "Sale date", CALYPSO_FINAL_TYPE_DATE),
+                        "ContractDataSaleTime",
+                        11,
+                        "Heure de chargement initial du contrat",
+                        CALYPSO_FINAL_TYPE_TIME),
                     make_calypso_final_element(
-                        "ContractSaleTime", 11, "Sale time", CALYPSO_FINAL_TYPE_TIME),
+                        "ContractDataReloadDate",
+                        14,
+                        "Date de rechargement du contrat",
+                        CALYPSO_FINAL_TYPE_DATE),
                     make_calypso_final_element(
-                        "ContractUnknownC", 36, "Unknown C", CALYPSO_FINAL_TYPE_NUMBER),
+                        "ContractDataValidityLimitDate",
+                        14,
+                        "Date limite pour une première utilisation du contrat",
+                        CALYPSO_FINAL_TYPE_DATE),
                     make_calypso_final_element(
-                        "ContractStatus", 8, "Status", CALYPSO_FINAL_TYPE_UNKNOWN),
+                        "ContractDataEndInhibitionDate",
+                        14,
+                        "Date jusqu’à laquelle la prèsence du contrat dans une liste de suspension est ignorée",
+                        CALYPSO_FINAL_TYPE_DATE),
                     make_calypso_final_element(
-                        "ContractUnknownD", 36, "Unknown D", CALYPSO_FINAL_TYPE_NUMBER),
+                        "ContractDataInhibition", 1, "Contrat invalide", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "ContractDataUsed",
+                        1,
+                        "Contrat déjà validé au moins une fois",
+                        CALYPSO_FINAL_TYPE_UNKNOWN),
                 }),
+            make_calypso_final_element(
+                "ContractUnknownE", 0, "Unknown E", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "ContractUnknownF", 0, "Unknown F", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "ContractUnknownG", 0, "Unknown G", CALYPSO_FINAL_TYPE_UNKNOWN),
         });
 
     return OpusContractStructure;
 }
 
 CalypsoApp* get_opus_event_structure() {
-    /*
-    En1545Container(
-                En1545FixedInteger.date(EVENT),
-                En1545FixedInteger.timeLocal(EVENT),
-                En1545FixedInteger("UnknownX", 19), // Possibly part of following bitmap
-                En1545Bitmap(
-                        En1545FixedInteger(EVENT_UNKNOWN_A, 8),
-                        En1545FixedInteger(EVENT_UNKNOWN_B, 8),
-                        En1545FixedInteger(EVENT_SERVICE_PROVIDER, 8),
-                        En1545FixedInteger(EVENT_UNKNOWN_C, 16),
-                        En1545FixedInteger(EVENT_ROUTE_NUMBER, 16),
-                        // How 32 bits are split among next 2 fields is unclear
-                        En1545FixedInteger(EVENT_UNKNOWN_D, 16),
-                        En1545FixedInteger(EVENT_UNKNOWN_E, 16),
-                        En1545FixedInteger(EVENT_CONTRACT_POINTER, 5),
-                        En1545Bitmap(
-                                En1545FixedInteger.date(EVENT_FIRST_STAMP),
-                                En1545FixedInteger.timeLocal(EVENT_FIRST_STAMP),
-                                En1545FixedInteger("EventDataSimulation", 1),
-                                En1545FixedInteger(EVENT_UNKNOWN_F, 4),
-                                En1545FixedInteger(EVENT_UNKNOWN_G, 4),
-                                En1545FixedInteger(EVENT_UNKNOWN_H, 4),
-                                En1545FixedInteger(EVENT_UNKNOWN_I, 4)
-                        )
-                )
-        )
-    */
     CalypsoApp* OpusEventStructure = malloc(sizeof(CalypsoApp));
 
     if(!OpusEventStructure) {
@@ -126,35 +124,51 @@ CalypsoApp* get_opus_event_structure() {
         "Event",
         9,
         (CalypsoElement[]){
-            make_calypso_final_element("EventUnknownA", 8, "Unknown A", CALYPSO_FINAL_TYPE_NUMBER),
-            make_calypso_final_element("EventUnknownB", 8, "Unknown B", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_final_element("EventUnknownA", 8, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN),
             make_calypso_final_element(
-                "EventServiceProvider", 8, "Service provider", CALYPSO_FINAL_TYPE_SERVICE_PROVIDER),
-            make_calypso_final_element("EventUnknownC", 16, "Unknown C", CALYPSO_FINAL_TYPE_NUMBER),
+                "EventResult", 8, "Code Résultat", CALYPSO_FINAL_TYPE_UNKNOWN),
             make_calypso_final_element(
-                "EventRouteNumber", 16, "Route number", CALYPSO_FINAL_TYPE_NUMBER),
-            make_calypso_final_element("EventUnknownD", 16, "Unknown D", CALYPSO_FINAL_TYPE_NUMBER),
-            make_calypso_final_element("EventUnknownE", 16, "Unknown E", CALYPSO_FINAL_TYPE_NUMBER),
+                "EventServiceProvider",
+                8,
+                "Identité de l’exploitant",
+                CALYPSO_FINAL_TYPE_SERVICE_PROVIDER),
             make_calypso_final_element(
-                "EventContractPointer", 5, "Contract pointer", CALYPSO_FINAL_TYPE_NUMBER),
+                "EventLocationId", 16, "Lieu de l’événement", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventRouteNumber", 16, "Référence de la ligne", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventUnknownD", 16, "Unknown D", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventUnknownE", 16, "Unknown E", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventContractPointer",
+                5,
+                "Référence du contrat concerné",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
             make_calypso_bitmap_element(
                 "EventData",
                 7,
                 (CalypsoElement[]){
                     make_calypso_final_element(
-                        "EventFirstStampDate", 14, "First stamp date", CALYPSO_FINAL_TYPE_DATE),
+                        "EventDataDateFirstStamp",
+                        14,
+                        "Date de la première montée",
+                        CALYPSO_FINAL_TYPE_DATE),
                     make_calypso_final_element(
-                        "EventFirstStampTime", 11, "First stamp time", CALYPSO_FINAL_TYPE_TIME),
+                        "EventDataTimeFirstStamp",
+                        11,
+                        "Heure de la première montée",
+                        CALYPSO_FINAL_TYPE_TIME),
                     make_calypso_final_element(
                         "EventDataSimulation", 1, "Simulation", CALYPSO_FINAL_TYPE_UNKNOWN),
                     make_calypso_final_element(
-                        "EventUnknownF", 4, "Unknown F", CALYPSO_FINAL_TYPE_NUMBER),
+                        "EventDataRouteDirection", 4, "Sens", CALYPSO_FINAL_TYPE_UNKNOWN),
                     make_calypso_final_element(
-                        "EventUnknownG", 4, "Unknown G", CALYPSO_FINAL_TYPE_NUMBER),
+                        "EventUnknownG", 4, "Unknown G", CALYPSO_FINAL_TYPE_UNKNOWN),
                     make_calypso_final_element(
-                        "EventUnknownH", 4, "Unknown H", CALYPSO_FINAL_TYPE_NUMBER),
+                        "EventUnknownH", 4, "Unknown H", CALYPSO_FINAL_TYPE_UNKNOWN),
                     make_calypso_final_element(
-                        "EventUnknownI", 4, "Unknown I", CALYPSO_FINAL_TYPE_NUMBER),
+                        "EventUnknownI", 4, "Unknown I", CALYPSO_FINAL_TYPE_UNKNOWN),
                 }),
         });
 
@@ -196,49 +210,66 @@ CalypsoApp* get_opus_env_holder_structure() {
                 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(
+            make_calypso_bitmap_element(
                 "EnvData",
-                2,
+                4,
                 (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),
+                    make_calypso_final_element(
+                        "EnvData_CardUtilisation",
+                        1,
+                        "Utilisation de la carte",
+                        CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EnvData4", 0, "Données complémentaires", CALYPSO_FINAL_TYPE_UNKNOWN),
                 }),
+            make_calypso_final_element("EnvUnknownA", 0, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element("EnvUnknownB", 0, "Unknown B", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element("EnvUnknownC", 0, "Unknown C", CALYPSO_FINAL_TYPE_UNKNOWN),
         });
 
     OpusEnvHolderStructure->container->elements[2] = make_calypso_bitmap_element(
         "Holder",
-        2,
+        8,
         (CalypsoElement[]){
-            make_calypso_container_element(
-                "HolderData",
-                5,
+            make_calypso_bitmap_element(
+                "HolderBirthBitmap",
+                2,
                 (CalypsoElement[]){
                     make_calypso_final_element(
-                        "HolderUnknownA", 3, "Unknown A", CALYPSO_FINAL_TYPE_NUMBER),
+                        "HolderBirthDate", 32, "Date de naissance", CALYPSO_FINAL_TYPE_DATE),
                     make_calypso_final_element(
-                        "HolderBirthDate", 8, "Birth date", CALYPSO_FINAL_TYPE_DATE),
-                    make_calypso_final_element(
-                        "HolderUnknownB", 13, "Unknown B", CALYPSO_FINAL_TYPE_NUMBER),
-                    make_calypso_final_element(
-                        "HolderProfile", 17, "Profile", CALYPSO_FINAL_TYPE_DATE),
-                    make_calypso_final_element(
-                        "HolderUnknownC", 8, "Unknown C", CALYPSO_FINAL_TYPE_NUMBER),
+                        "HolderBirthUnknownA", 0, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN),
                 }),
-            make_calypso_final_element("HolderUnknownD", 8, "Unknown D", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_repeater_element(
+                "HolderProfilesList",
+                4,
+                make_calypso_bitmap_element(
+                    "HolderProfile",
+                    3,
+                    (CalypsoElement[]){
+                        make_calypso_final_element(
+                            "HolderProfileNumber", 6, "Numéro de profil", CALYPSO_FINAL_TYPE_NUMBER),
+                        make_calypso_final_element(
+                            "HolderProfileDate", 14, "Date de profil", CALYPSO_FINAL_TYPE_DATE),
+                        make_calypso_final_element(
+                            "HolderProfileUnknownA", 0, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    })),
+            make_calypso_final_element(
+                "HolderData_Language", 6, "Langue de l'utilisateur", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderUnknownA", 0, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderUnknownB", 0, "Unknown B", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderUnknownC", 0, "Unknown C", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderUnknownD", 0, "Unknown D", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderUnknownE", 0, "Unknown E", CALYPSO_FINAL_TYPE_UNKNOWN),
         });
 
     return OpusEnvHolderStructure;

+ 81 - 33
api/calypso/transit/navigo.c

@@ -222,25 +222,49 @@ char* get_navigo_station(
     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) {
+            const char* sncf_stations_path = APP_ASSETS_PATH("navigo/sncf_stations.txt");
+            Storage* storage = furi_record_open(RECORD_STORAGE);
+
+            Stream* stream = file_stream_alloc(storage);
+            FuriString* line = furi_string_alloc();
+
+            char* found_station_name = NULL;
+
+            if(file_stream_open(stream, sncf_stations_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+                while(stream_read_line(stream, line)) {
+                    // file is in csv format: station_group_id,station_id,station_sub_id,station_name
+                    // search for the station
+                    furi_string_replace_all(line, "\r", "");
+                    furi_string_replace_all(line, "\n", "");
+                    const char* string_line = furi_string_get_cstr(line);
+                    char* string_line_copy = strdup(string_line);
+                    if(!string_line_copy) {
+                        return "Unknown";
+                    }
+                    int line_station_group_id =
+                        atoi(get_token(string_line_copy, ",", string_line_copy));
+                    int line_station_id = atoi(get_token(string_line_copy, ",", string_line_copy));
+                    int line_station_sub_id =
+                        atoi(get_token(string_line_copy, ",", string_line_copy));
+                    if(line_station_group_id == station_group_id &&
+                       line_station_id == station_id && line_station_sub_id == station_sub_id) {
+                        found_station_name =
+                            strdup(get_token(string_line_copy, ",", string_line_copy));
+                        free(string_line_copy);
                         break;
                     }
+                    free(string_line_copy);
                 }
-                free(station_name);
-                if(token) {
-                    return token;
-                }
+            } else {
+                FURI_LOG_E("Metroflip:Scene:Calypso", "Failed to open sncf_stations.txt");
+            }
+
+            furi_string_free(line);
+            file_stream_close(stream);
+            stream_free(stream);
+
+            if(found_station_name) {
+                return found_station_name;
             }
         }
         // cast station_group_id-station_id-station_sub_id to a string
@@ -254,12 +278,47 @@ char* get_navigo_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;
+            const char* ratp_stations_path = APP_ASSETS_PATH("navigo/ratp_stations.txt");
+            Storage* storage = furi_record_open(RECORD_STORAGE);
+
+            Stream* stream = file_stream_alloc(storage);
+            FuriString* line = furi_string_alloc();
+
+            char* found_station_name = NULL;
+
+            if(file_stream_open(stream, ratp_stations_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+                while(stream_read_line(stream, line)) {
+                    // file is in csv format: station_group_id,station_id,station_name
+                    // search for the station
+                    furi_string_replace_all(line, "\r", "");
+                    furi_string_replace_all(line, "\n", "");
+                    const char* string_line = furi_string_get_cstr(line);
+                    char* string_line_copy = strdup(string_line);
+                    if(!string_line_copy) {
+                        return "Unknown";
+                    }
+                    int line_station_group_id =
+                        atoi(get_token(string_line_copy, ",", string_line_copy));
+                    int line_station_id = atoi(get_token(string_line_copy, ",", string_line_copy));
+                    if(line_station_group_id == station_group_id &&
+                       line_station_id == station_id) {
+                        found_station_name =
+                            strdup(get_token(string_line_copy, ",", string_line_copy));
+                        free(string_line_copy);
+                        break;
+                    }
+                    free(string_line_copy);
                 }
+            } else {
+                FURI_LOG_E("Metroflip:Scene:Calypso", "Failed to open ratp_stations.txt");
+            }
+
+            furi_string_free(line);
+            file_stream_close(stream);
+            stream_free(stream);
+
+            if(found_station_name) {
+                return found_station_name;
             }
         }
         // cast station_group_id-station_id to a string
@@ -583,7 +642,7 @@ void show_navigo_contract_info(NavigoCardContract* contract, FuriString* parsed_
     locale_format_datetime_cat(parsed_data, &contract->start_date, false);
     furi_string_cat_printf(parsed_data, "\n");
     if(contract->end_date_available) {
-        furi_string_cat_printf(parsed_data, "\nto: ");
+        furi_string_cat_printf(parsed_data, "to: ");
         locale_format_datetime_cat(parsed_data, &contract->end_date, false);
         furi_string_cat_printf(parsed_data, "\n");
     }
@@ -599,17 +658,6 @@ void show_navigo_contract_info(NavigoCardContract* contract, FuriString* parsed_
         furi_string_cat_printf(
             parsed_data, "Payment Method: %s\n", get_pay_method(contract->pay_method));
     }
-    if(contract->end_date_available) {
-        furi_string_cat_printf(parsed_data, "Valid\nfrom: ");
-        locale_format_datetime_cat(parsed_data, &contract->start_date, false);
-        furi_string_cat_printf(parsed_data, "\nto: ");
-        locale_format_datetime_cat(parsed_data, &contract->end_date, false);
-        furi_string_cat_printf(parsed_data, "\n");
-    } else {
-        furi_string_cat_printf(parsed_data, "Valid from\n");
-        locale_format_datetime_cat(parsed_data, &contract->start_date, false);
-        furi_string_cat_printf(parsed_data, "\n");
-    }
     if(contract->zones_available) {
         furi_string_cat_printf(parsed_data, "%s\n", get_zones(contract->zones));
     }

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

@@ -4,6 +4,9 @@
 #include <datetime.h>
 #include <stdbool.h>
 #include <furi.h>
+#include <storage/storage.h>
+#include <toolbox/stream/stream.h>
+#include <toolbox/stream/file_stream.h>
 
 #ifndef NAVIGO_H
 #define NAVIGO_H

+ 0 - 653
api/calypso/transit/navigo_lists.h

@@ -1,379 +1,6 @@
 #ifndef NAVIGO_LISTS_H
 #define NAVIGO_LISTS_H
 
-static const char* NAVIGO_RATP_LOCATION_LIST[32][16] =
-    {[1] =
-         {[0] = "Cite",
-          [1] = "Saint-Michel",
-          [4] = "Odeon",
-          [5] = "Cluny - La Sorbonne",
-          [6] = "Maubert - Mutualite",
-          [7] = "Luxembourg",
-          [8] = "Châtelet",
-          [9] = "Les Halles",
-          [10] = "Les Halles",
-          [12] = "Louvre - Rivoli",
-          [13] = "Pont Neuf",
-          [14] = "Cite",
-          [15] = "Hotel de Ville"},
-     [2] =
-         {[0] = "Rennes",
-          [2] = "Cambronne",
-          [3] = "Sevres - Lecourbe",
-          [4] = "Segur",
-          [6] = "Saint-François-Xavier",
-          [7] = "Duroc",
-          [8] = "Vaneau",
-          [9] = "Sevres - Babylone",
-          [10] = "Rue du Bac",
-          [11] = "Rennes",
-          [12] = "Saint-Sulpice",
-          [14] = "Mabillon",
-          [15] = "Saint-Germain-des-Pres"},
-     [3] =
-         {[0] = "Villette",
-          [4] = "Porte de la Villette",
-          [5] = "Aubervilliers - Pantin - Quatre Chemins",
-          [6] = "Fort d'Aubervilliers",
-          [7] = "La Courneuve - 8 Mai 1945",
-          [9] = "Hoche",
-          [10] = "Eglise de Pantin",
-          [11] = "Bobigny - Pantin - Raymond Queneau",
-          [12] = "Bobigny - Pablo Picasso"},
-     [4] =
-         {[0] = "Montparnasse",
-          [2] = "Pernety",
-          [3] = "Plaisance",
-          [4] = "Gaite",
-          [6] = "Edgar Quinet",
-          [7] = "Vavin",
-          [8] = "Montparnasse - Bienvenue",
-          [12] = "Saint-Placide",
-          [14] = "Notre-Dame-des-Champs"},
-     [5] =
-         {[0] = "Nation",
-          [2] = "Robespierre",
-          [3] = "Porte de Montreuil",
-          [4] = "Maraichers",
-          [5] = "Buzenval",
-          [6] = "Rue des Boulets",
-          [7] = "Porte de Vincennes",
-          [9] = "Picpus",
-          [10] = "Nation",
-          [12] = "Avron",
-          [13] = "Alexandre Dumas"},
-     [6] =
-         {[0] = "Saint-Lazare",
-          [1] = "Malesherbes",
-          [2] = "Monceau",
-          [3] = "Villiers",
-          [4] = "Quatre-Septembre",
-          [5] = "Opera",
-          [6] = "Auber",
-          [7] = "Havre - Caumartin",
-          [8] = "Saint-Lazare",
-          [9] = "Saint-Lazare",
-          [10] = "Saint-Augustin",
-          [12] = "Europe",
-          [13] = "Liege"},
-     [7] =
-         {[0] = "Auteuil",
-          [3] = "Porte de Saint-Cloud",
-          [7] = "Porte d'Auteuil",
-          [8] = "eglise d'Auteuil",
-          [9] = "Michel-Ange - Auteuil",
-          [10] = "Michel-Ange - Molitor",
-          [11] = "Chardon-Lagache",
-          [12] = "Mirabeau",
-          [14] = "Exelmans",
-          [15] = "Jasmin"},
-     [8] =
-         {[0] = "Republique",
-          [1] = "Rambuteau",
-          [3] = "Arts et Metiers",
-          [4] = "Jacques Bonsergent",
-          [5] = "Goncourt",
-          [6] = "Temple",
-          [7] = "Republique",
-          [10] = "Oberkampf",
-          [11] = "Parmentier",
-          [12] = "Filles du Calvaire",
-          [13] = "Saint-Sebastien - Froissart",
-          [14] = "Richard-Lenoir",
-          [15] = "Saint-Ambroise"},
-     [9] =
-         {[0] = "Austerlitz",
-          [1] = "Quai de la Gare",
-          [2] = "Chevaleret",
-          [4] = "Saint-Marcel",
-          [7] = "Gare d'Austerlitz",
-          [8] = "Gare de Lyon",
-          [10] = "Quai de la Rapee"},
-     [10] =
-         {[0] = "Invalides",
-          [1] = "Champs-elysees - Clemenceau",
-          [2] = "Concorde",
-          [3] = "Madeleine",
-          [4] = "Bir-Hakeim",
-          [7] = "ecole Militaire",
-          [8] = "La Tour-Maubourg",
-          [9] = "Invalides",
-          [11] = "Saint-Denis - Universite",
-          [12] = "Varenne",
-          [13] = "Assemblee nationale",
-          [14] = "Solferino"},
-     [11] =
-         {[0] = "Sentier",
-          [1] = "Tuileries",
-          [2] = "Palais Royal - Musee du Louvre",
-          [3] = "Pyramides",
-          [4] = "Bourse",
-          [6] = "Grands Boulevards",
-          [7] = "Richelieu - Drouot",
-          [8] = "Bonne Nouvelle",
-          [10] = "Strasbourg - Saint-Denis",
-          [11] = "Château d'Eau",
-          [13] = "Sentier",
-          [14] = "Reaumur - Sebastopol",
-          [15] = "etienne Marcel"},
-     [12] =
-         {[0] = "ile Saint-Louis",
-          [1] = "Faidherbe - Chaligny",
-          [2] = "Reuilly - Diderot",
-          [3] = "Montgallet",
-          [4] = "Censier - Daubenton",
-          [5] = "Place Monge",
-          [6] = "Cardinal Lemoine",
-          [7] = "Jussieu",
-          [8] = "Sully - Morland",
-          [9] = "Pont Marie",
-          [10] = "Saint-Paul",
-          [12] = "Bastille",
-          [13] = "Chemin Vert",
-          [14] = "Breguet - Sabin",
-          [15] = "Ledru-Rollin"},
-     [13] =
-         {[0] = "Daumesnil",
-          [1] = "Porte Doree",
-          [3] = "Porte de Charenton",
-          [7] = "Bercy",
-          [8] = "Dugommier",
-          [10] = "Michel Bizot",
-          [11] = "Daumesnil",
-          [12] = "Bel-Air"},
-     [14] =
-         {[0] = "Italie",
-          [2] = "Porte de Choisy",
-          [3] = "Porte d'Italie",
-          [4] = "Cite universitaire",
-          [9] = "Maison Blanche",
-          [10] = "Tolbiac",
-          [11] = "Nationale",
-          [12] = "Campo-Formio",
-          [13] = "Les Gobelins",
-          [14] = "Place d'Italie",
-          [15] = "Corvisart"},
-     [15] =
-         {[0] = "Denfert",
-          [1] = "Cour Saint-Emilion",
-          [2] = "Porte d'Orleans",
-          [3] = "Bibliotheque François Mitterrand",
-          [4] = "Mouton-Duvernet",
-          [5] = "Alesia",
-          [6] = "Olympiades",
-          [8] = "Glaciere",
-          [9] = "Saint-Jacques",
-          [10] = "Raspail",
-          [13] = "Denfert-Rochereau",
-          [14] = "Denfert-Rochereau"},
-     [16] =
-         {[0] = "Felix Faure",
-          [1] = "Falguiere",
-          [2] = "Pasteur",
-          [3] = "Volontaires",
-          [4] = "Vaugirard",
-          [5] = "Convention",
-          [6] = "Porte de Versailles",
-          [9] = "Balard",
-          [10] = "Lourmel",
-          [11] = "Boucicaut",
-          [12] = "Felix Faure",
-          [13] = "Charles Michels",
-          [14] = "Javel - Andre Citroen"},
-     [17] =
-         {[0] = "Passy",
-          [2] = "Porte Dauphine",
-          [4] = "La Motte-Picquet - Grenelle",
-          [5] = "Commerce",
-          [6] = "Avenue emile Zola",
-          [7] = "Dupleix",
-          [8] = "Passy",
-          [9] = "Ranelagh",
-          [11] = "La Muette",
-          [13] = "Rue de la Pompe",
-          [14] = "Boissiere",
-          [15] = "Trocadero"},
-     [18] =
-         {[0] = "Etoile",
-          [1] = "Iena",
-          [3] = "Alma - Marceau",
-          [4] = "Miromesnil",
-          [5] = "Saint-Philippe du Roule",
-          [7] = "Franklin D. Roosevelt",
-          [8] = "George V",
-          [9] = "Kleber",
-          [10] = "Victor Hugo",
-          [11] = "Argentine",
-          [12] = "Charles de Gaulle - Itoile",
-          [14] = "Ternes",
-          [15] = "Courcelles"},
-     [19] =
-         {[0] = "Clichy - Saint Ouen",
-          [1] = "Mairie de Clichy",
-          [2] = "Gabriel Peri",
-          [3] = "Les Agnettes",
-          [4] = "Asnieres - Gennevilliers - Les Courtilles",
-          [9] = "La Chapelle)",
-          [10] = "Garibaldi",
-          [11] = "Mairie de Saint-Ouen",
-          [13] = "Carrefour Pleyel",
-          [14] = "Saint-Denis - Porte de Paris",
-          [15] = "Basilique de Saint-Denis"},
-     [20] =
-         {[0] = "Montmartre",
-          [1] = "Porte de Clignancourt",
-          [6] = "Porte de la Chapelle",
-          [7] = "Marx Dormoy",
-          [9] = "Marcadet - Poissonniers",
-          [10] = "Simplon",
-          [11] = "Jules Joffrin",
-          [12] = "Lamarck - Caulaincourt"},
-     [21] =
-         {[0] = "Lafayette",
-          [1] = "Chaussee d'Antin - La Fayette",
-          [2] = "Le Peletier",
-          [3] = "Cadet",
-          [4] = "Château Rouge",
-          [7] = "Barbes - Rochechouart",
-          [8] = "Gare du Nord",
-          [9] = "Gare de l'Est",
-          [10] = "Poissonniere",
-          [11] = "Château-Landon"},
-     [22] =
-         {[0] = "Buttes Chaumont",
-          [1] = "Porte de Pantin",
-          [2] = "Ourcq",
-          [4] = "Corentin Cariou",
-          [6] = "Crimee",
-          [8] = "Riquet",
-          [9] = "La Chapelle",
-          [10] = "Louis Blanc",
-          [11] = "Stalingrad",
-          [12] = "Jaures",
-          [13] = "Laumiere",
-          [14] = "Bolivar",
-          [15] = "Colonel Fabien"},
-     [23] =
-         {[0] = "Belleville",
-          [2] = "Porte des Lilas",
-          [3] = "Mairie des Lilas",
-          [4] = "Porte de Bagnolet",
-          [5] = "Gallieni",
-          [8] = "Place des Fetes",
-          [9] = "Botzaris",
-          [10] = "Danube",
-          [11] = "Pre Saint-Gervais",
-          [13] = "Buttes Chaumont",
-          [14] = "Jourdain",
-          [15] = "Telegraphe"},
-     [24] =
-         {[0] = "Pere Lachaise",
-          [1] = "Voltaire",
-          [2] = "Charonne",
-          [4] = "Pere Lachaise",
-          [5] = "Menilmontant",
-          [6] = "Rue Saint-Maur",
-          [7] = "Philippe Auguste",
-          [8] = "Saint-Fargeau",
-          [9] = "Pelleport",
-          [10] = "Gambetta",
-          [12] = "Belleville",
-          [13] = "Couronnes",
-          [14] = "Pyrenees"},
-     [25] =
-         {[0] = "Charenton",
-          [2] = "Croix de Chavaux",
-          [3] = "Mairie de Montreuil",
-          [4] = "Maisons-Alfort - Les Juilliottes",
-          [5] = "Creteil - L'echat",
-          [6] = "Creteil - Universite",
-          [7] = "Creteil - Prefecture",
-          [8] = "Saint-Mande",
-          [10] = "Berault",
-          [11] = "Château de Vincennes",
-          [12] = "Liberte",
-          [13] = "Charenton - ecoles",
-          [14] = "ecole veterinaire de Maisons-Alfort",
-          [15] = "Maisons-Alfort - Stade"},
-     [26] =
-         {[0] = "Ivry - Villejuif",
-          [3] = "Porte d'Ivry",
-          [4] = "Pierre et Marie Curie",
-          [5] = "Mairie d'Ivry",
-          [6] = "Le Kremlin-Bicetre",
-          [7] = "Villejuif - Leo Lagrange",
-          [8] = "Villejuif - Paul Vaillant-Couturier",
-          [9] = "Villejuif - Louis Aragon",
-          [11] = "Villejuif - Institut Gustave Roussy"},
-     [27] =
-         {[0] = "Vanves",
-          [2] = "Porte de Vanves",
-          [7] = "Malakoff - Plateau de Vanves",
-          [8] = "Malakoff - Rue etienne Dolet",
-          [9] = "Châtillon - Montrouge"},
-     [28] =
-         {[0] = "Issy",
-          [2] = "Corentin Celton",
-          [3] = "Mairie d'Issy",
-          [8] = "Marcel Sembat",
-          [9] = "Billancourt",
-          [10] = "Pont de Sevres"},
-     [29] =
-         {[0] = "Levallois",
-          [4] = "Boulogne - Jean Jaures",
-          [5] = "Boulogne - Pont de Saint-Cloud",
-          [8] = "Les Sablons",
-          [9] = "Pont de Neuilly",
-          [10] = "Esplanade de la Defense",
-          [11] = "La Defense",
-          [12] = "Porte de Champerret",
-          [13] = "Louise Michel",
-          [14] = "Anatole France",
-          [15] = "Pont de Levallois - Becon"},
-     [30] =
-         {[0] = "Pereire",
-          [1] = "Porte Maillot",
-          [4] = "Wagram",
-          [5] = "Pereire",
-          [8] = "Brochant",
-          [9] = "Porte de Clichy",
-          [12] = "Guy Moquet",
-          [13] = "Porte de Saint-Ouen"},
-     [31] = {
-         [0] = "Pigalle",
-         [2] = "Funiculaire de Montmartre (station inferieure)",
-         [3] = "Funiculaire de Montmartre (station superieure)",
-         [4] = "Anvers",
-         [5] = "Abbesses",
-         [6] = "Pigalle",
-         [7] = "Blanche",
-         [8] = "Trinite - d'Estienne d'Orves",
-         [9] = "Notre-Dame-de-Lorette",
-         [10] = "Saint-Georges",
-         [12] = "Rome",
-         [13] = "Place de Clichy",
-         [14] = "La Fourche"}};
-
 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",
@@ -387,284 +14,4 @@ static const char* NAVIGO_SNCF_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_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",
-         [4] = "Saint-Maur Creteil",
-         [5] = "Le Parc de Saint-Maur",
-         [6] = "Champigny",
-         [7] = "La Varenne-Chennevieres",
-         [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"},
-    [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",
-         [8] = "Lozere",
-         [9] = "Le Guichet|Orsay-Ville",
-         [10] =
-             "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",
-         [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"},
-    [21] =
-        {[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",
-         [11] = "Ozoir-la-Ferriere",
-         [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",
-         [16] =
-             "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"},
-    [22] =
-        {[7] =
-             "Allee de la Tour-Rendez-Vous|La Remise-a-Jorelle|Les Coquetiers|Les Pavillons-sous-Bois",
-         [8] = "Gargan",
-         [9] = "Freinville Sevran|L'Abbaye"},
-    [23] =
-        {[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",
-         [7] = "Noisy-le-Grand (Mont d'Est)",
-         [8] = "Noisy-Champs",
-         [10] = "Lognes|Noisiel|Torcy",
-         [11] = "Bussy-Saint-Georges",
-         [12] = "Val d'europe",
-         [13] = "Marne-la-Vallee Chessy"},
-    [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",
-         [8] = "Vaucresson",
-         [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"},
-    [32] =
-        {[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",
-         [14] = "Epone Mezieres",
-         [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"},
-    [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"},
-    [40] =
-        {[1] = "Gare de Lyon",
-         [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",
-         [14] = "La Ferte-Alais",
-         [16] = "Boutigny|Maisse",
-         [17] = "Boigneville|Buno-Gironville"},
-    [41] =
-        {[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",
-         [7] = "Villeneuve-le-Roi",
-         [8] = "Ablon",
-         [9] = "Athis-Mons"},
-    [42] =
-        {[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",
-         [16] = "Saint-Martin d'Etampes",
-         [17] = "Guillerval"},
-    [43] =
-        {[9] = "Montgeron Crosne|Yerres",
-         [10] = "Brunoy",
-         [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",
-         [17] =
-             "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",
-         [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"},
-    [45] =
-        {[10] = "Grigny-Centre",
-         [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",
-         [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"},
-    [51] =
-        {[4] = "La Courneuve-Aubervilliers|La Plaine-Stade de France",
-         [5] = "Le Bourget",
-         [7] = "Blanc-Mesnil|Drancy",
-         [8] = "Aulnay-sous-Bois",
-         [9] = "Sevran Livry|Vert-Galant",
-         [10] = "Villeparisis",
-         [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"},
-    [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"},
-    [54] =
-        {[8] = "Cernay",
-         [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"},
-    [55] =
-        {[0] =
-             "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",
-         [4] = "Les Gresillons",
-         [5] = "Gennevilliers",
-         [6] = "Epinay-sur-Seine",
-         [7] = "Saint-Gratien"},
-    [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",
-         [7] = "Versailles-Chantiers",
-         [10] = "Saint-Cyr",
-         [11] = "Saint-Quentin-en-Yvelines - Montigny le Bretonneux|Trappes",
-         [12] = "Coignieres|La Verriere",
-         [13] = "Les Essarts-le-Roi",
-         [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",
-         [15] =
-             "Garancieres La-Queue|Montfort-l'Amaury Mere|Orgerus Behoust|Tacoigneres Richebourg|Villiers Neauphle Pontchartrain",
-         [16] = "Houdan"},
-    [63] = {[7] = "Porchefontaine|Versailles-Rive Gauche"},
-    [64] =
-        {[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",
-         [5] = "Meudon-Val-Fleury"},
-    [65] =
-        {[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"},
-    [70] =
-        {[9] = "Parc des Expositions|Sevran-Beaudottes|Villepinte",
-         [10] = "Aeroport Charles de Gaulle"},
-    [72] = {[7] = "Sannois"},
-    [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",
-         [10] = "Chemin d'Antony",
-         [12] = "Massy-Verrieres|Arpajon"},
-    [76] =
-        {[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"},
-};
-
 #endif

+ 48 - 18
api/calypso/transit/opus.c

@@ -17,6 +17,8 @@ const char* get_opus_service_provider(int provider) {
         return "STL";
     case 0x10:
         return "STLevis";
+    case 0x20:
+        return "Chrono";
     default: {
         char* provider_str = malloc(10 * sizeof(char));
         if(!provider_str) {
@@ -28,11 +30,20 @@ const char* get_opus_service_provider(int provider) {
     }
 }
 
-const char* get_opus_transport_type(int route_number) {
-    if(route_number >= 0x01 && route_number <= 0x04) {
-        return "Metro";
-    } else {
+const char* get_opus_transport_type(int location_id) {
+    switch(location_id) {
+    case 0x65:
         return "Bus";
+    case 0xc9:
+        return "Metro";
+    default: {
+        char* location_str = malloc(9 * sizeof(char));
+        if(!location_str) {
+            return "Unknown";
+        }
+        snprintf(location_str, 9, "0x%02X", location_id);
+        return location_str;
+    }
     }
 }
 
@@ -82,18 +93,21 @@ void show_opus_event_info(
     furi_string_cat_printf(
         parsed_data,
         "%s %s\n",
-        get_opus_transport_type(event->route_number),
+        get_opus_transport_type(event->location_id),
         get_opus_transport_line(event->route_number));
     furi_string_cat_printf(
         parsed_data, "Transporter: %s\n", get_opus_service_provider(event->service_provider));
-    if(event->used_contract_available) {
-        furi_string_cat_printf(
-            parsed_data,
-            "Contract: %d - %s\n",
-            event->used_contract,
-            get_opus_tariff(contracts[event->used_contract - 1].tariff));
-    }
+    furi_string_cat_printf(parsed_data, "Result: %d\n", event->result);
+    furi_string_cat_printf(
+        parsed_data,
+        "Contract: %d - %s\n",
+        event->used_contract,
+        get_opus_tariff(contracts[event->used_contract - 1].tariff));
+    furi_string_cat_printf(parsed_data, "Simulation: %s\n", event->simulation ? "true" : "false");
+    furi_string_cat_printf(parsed_data, "Date: ");
     locale_format_datetime_cat(parsed_data, &event->date, true);
+    furi_string_cat_printf(parsed_data, "\nFirst stamp: ");
+    locale_format_datetime_cat(parsed_data, &event->first_stamp_date, true);
     furi_string_cat_printf(parsed_data, "\n");
 }
 
@@ -101,17 +115,24 @@ void show_opus_contract_info(OpusCardContract* contract, FuriString* parsed_data
     furi_string_cat_printf(parsed_data, "Type: %s\n", get_opus_tariff(contract->tariff));
     furi_string_cat_printf(
         parsed_data, "Provider: %s\n", get_opus_service_provider(contract->provider));
-    furi_string_cat_printf(parsed_data, "Valid\nfrom: ");
+    furi_string_cat_printf(parsed_data, "Valid from: ");
     locale_format_datetime_cat(parsed_data, &contract->start_date, false);
     furi_string_cat_printf(parsed_data, "\nto: ");
     locale_format_datetime_cat(parsed_data, &contract->end_date, false);
-    furi_string_cat_printf(parsed_data, "\n");
-    // furi_string_cat_printf(parsed_data, "Sold on: ");
-    // locale_format_datetime_cat(parsed_data, &contract->sale_date, false);
-    // furi_string_cat_printf(parsed_data, "\nStatus: %d\n", contract->status);
+    furi_string_cat_printf(parsed_data, "\nSold on: ");
+    locale_format_datetime_cat(parsed_data, &contract->sale_date, true);
+    furi_string_cat_printf(
+        parsed_data, "\nSales Agent: %s\n", get_opus_service_provider(contract->sale_agent));
+    if(contract->inhibition) {
+        furi_string_cat_printf(parsed_data, "Contract inhibited\n");
+    }
+    furi_string_cat_printf(parsed_data, "Used: %s\n", contract->used ? "true" : "false");
 }
 
-void show_opus_environment_info(OpusCardEnv* environment, FuriString* parsed_data) {
+void show_opus_environment_info(
+    OpusCardEnv* environment,
+    OpusCardHolder* holder,
+    FuriString* parsed_data) {
     furi_string_cat_printf(parsed_data, "App Version: %d\n", environment->app_version);
     furi_string_cat_printf(
         parsed_data, "Country: %s\n", get_country_string(environment->country_num));
@@ -121,5 +142,14 @@ void show_opus_environment_info(OpusCardEnv* environment, FuriString* parsed_dat
         get_network_string(guess_card_type(environment->country_num, environment->network_num)));
     furi_string_cat_printf(parsed_data, "End of validity:\n");
     locale_format_datetime_cat(parsed_data, &environment->end_dt, false);
+    furi_string_cat_printf(parsed_data, "\nIssuer: %d\n", environment->issuer_id);
+    furi_string_cat_printf(
+        parsed_data, "Card status: %s\n", environment->card_status ? "true" : "false");
+    furi_string_cat_printf(
+        parsed_data,
+        "Card utilisation: %s\n",
+        environment->card_utilisation ? "Used" : "Not used");
+    furi_string_cat_printf(parsed_data, "Holder birth date: ");
+    locale_format_datetime_cat(parsed_data, &holder->birth_date, false);
     furi_string_cat_printf(parsed_data, "\n");
 }

+ 4 - 1
api/calypso/transit/opus.h

@@ -12,6 +12,9 @@ void show_opus_event_info(
 
 void show_opus_contract_info(OpusCardContract* contract, FuriString* parsed_data);
 
-void show_opus_environment_info(OpusCardEnv* environment, FuriString* parsed_data);
+void show_opus_environment_info(
+    OpusCardEnv* environment,
+    OpusCardHolder* holder,
+    FuriString* parsed_data);
 
 #endif // OPUS_H

+ 19 - 5
api/calypso/transit/opus_i.h

@@ -6,23 +6,35 @@
 
 typedef struct {
     int service_provider;
+    int result;
     int route_number;
-    bool route_number_available;
+    int route_direction;
+    int location_id;
     int used_contract;
-    bool used_contract_available;
+    bool simulation;
     DateTime date;
+    DateTime first_stamp_date;
 } OpusCardEvent;
 
 typedef struct {
     int app_version;
     int country_num;
     int network_num;
+    int issuer_id;
+    bool card_status;
+    bool card_utilisation;
     DateTime end_dt;
 } OpusCardEnv;
 
 typedef struct {
-    int card_status;
-    int commercial_id;
+    int number;
+    DateTime date;
+} OpusCardHolderProfile;
+
+typedef struct {
+    DateTime birth_date;
+    OpusCardHolderProfile profiles[4];
+    int language;
 } OpusCardHolder;
 
 typedef struct {
@@ -30,8 +42,10 @@ typedef struct {
     int tariff;
     DateTime start_date;
     DateTime end_date;
+    int sale_agent;
     DateTime sale_date;
-    int status;
+    bool inhibition;
+    bool used;
     bool present;
 } OpusCardContract;
 

+ 1 - 0
application.fam

@@ -10,4 +10,5 @@ App(
     fap_description="An implementation of metrodroid on the flipper",
     fap_author="luu176",
     fap_icon_assets="images",  # Image assets to compile for this application
+    fap_file_assets="files",
 )

+ 341 - 0
files/navigo/ratp_stations.txt

@@ -0,0 +1,341 @@
+station_group_id,station_id,station_name
+1,0,Cite
+1,1,Saint-Michel
+1,4,Odeon
+1,5,Cluny - La Sorbonne
+1,6,Maubert - Mutualite
+1,7,Luxembourg
+1,8,Châtelet
+1,9,Les Halles
+1,10,Les Halles
+1,12,Louvre - Rivoli
+1,13,Pont Neuf
+1,14,Cite
+1,15,Hotel de Ville
+2,0,Rennes
+2,2,Cambronne
+2,3,Sevres - Lecourbe
+2,4,Segur
+2,6,Saint-François-Xavier
+2,7,Duroc
+2,8,Vaneau
+2,9,Sevres - Babylone
+2,10,Rue du Bac
+2,11,Rennes
+2,12,Saint-Sulpice
+2,14,Mabillon
+2,15,Saint-Germain-des-Pres
+3,0,Villette
+3,4,Porte de la Villette
+3,5,Aubervilliers - Pantin - Quatre Chemins
+3,6,Fort d'Aubervilliers
+3,7,La Courneuve - 8 Mai 1945
+3,9,Hoche
+3,10,Eglise de Pantin
+3,11,Bobigny - Pantin - Raymond Queneau
+3,12,Bobigny - Pablo Picasso
+4,0,Montparnasse
+4,2,Pernety
+4,3,Plaisance
+4,4,Gaite
+4,6,Edgar Quinet
+4,7,Vavin
+4,8,Montparnasse - Bienvenue
+4,12,Saint-Placide
+4,14,Notre-Dame-des-Champs
+5,0,Nation
+5,2,Robespierre
+5,3,Porte de Montreuil
+5,4,Maraichers
+5,5,Buzenval
+5,6,Rue des Boulets
+5,7,Porte de Vincennes
+5,9,Picpus
+5,10,Nation
+5,12,Avron
+5,13,Alexandre Dumas
+6,0,Saint-Lazare
+6,1,Malesherbes
+6,2,Monceau
+6,3,Villiers
+6,4,Quatre-Septembre
+6,5,Opera
+6,6,Auber
+6,7,Havre - Caumartin
+6,8,Saint-Lazare
+6,9,Saint-Lazare
+6,10,Saint-Augustin
+6,12,Europe
+6,13,Liege
+7,0,Auteuil
+7,3,Porte de Saint-Cloud
+7,7,Porte d'Auteuil
+7,8,Eglise d'Auteuil
+7,9,Michel-Ange - Auteuil
+7,10,Michel-Ange - Molitor
+7,11,Chardon-Lagache
+7,12,Mirabeau
+7,14,Exelmans
+7,15,Jasmin
+8,0,Republique
+8,1,Rambuteau
+8,3,Arts et Metiers
+8,4,Jacques Bonsergent
+8,5,Goncourt
+8,6,Temple
+8,7,Republique
+8,10,Oberkampf
+8,11,Parmentier
+8,12,Filles du Calvaire
+8,13,Saint-Sebastien - Froissart
+8,14,Richard-Lenoir
+8,15,Saint-Ambroise
+9,0,Austerlitz
+9,1,Quai de la Gare
+9,2,Chevaleret
+9,4,Saint-Marcel
+9,7,Gare d'Austerlitz
+9,8,Gare de Lyon
+9,10,Quai de la Rapee
+10,0,Invalides
+10,1,Champs-Elysees - Clemenceau
+10,2,Concorde
+10,3,Madeleine
+10,4,Bir-Hakeim
+10,7,Ecole Militaire
+10,8,La Tour-Maubourg
+10,9,Invalides
+10,11,Saint-Denis - Universite
+10,12,Varenne
+10,13,Assemblee nationale
+10,14,Solferino
+11,0,Sentier
+11,1,Tuileries
+11,2,Palais Royal - Musee du Louvre
+11,3,Pyramides
+11,4,Bourse
+11,6,Grands Boulevards
+11,7,Richelieu - Drouot
+11,8,Bonne Nouvelle
+11,10,Strasbourg - Saint-Denis
+11,11,Château d'Eau
+11,13,Sentier
+11,14,Reaumur - Sebastopol
+11,15,etienne Marcel
+12,0,Ile Saint-Louis
+12,1,Faidherbe - Chaligny
+12,2,Reuilly - Diderot
+12,3,Montgallet
+12,4,Censier - Daubenton
+12,5,Place Monge
+12,6,Cardinal Lemoine
+12,7,Jussieu
+12,8,Sully - Morland
+12,9,Pont Marie
+12,10,Saint-Paul
+12,12,Bastille
+12,13,Chemin Vert
+12,14,Breguet - Sabin
+12,15,Ledru-Rollin
+13,0,Daumesnil
+13,1,Porte Doree
+13,3,Porte de Charenton
+13,7,Bercy
+13,8,Dugommier
+13,10,Michel Bizot
+13,11,Daumesnil
+13,12,Bel-Air
+14,0,Italie
+14,2,Porte de Choisy
+14,3,Porte d'Italie
+14,4,Cite universitaire
+14,9,Maison Blanche
+14,10,Tolbiac
+14,11,Nationale
+14,12,Campo-Formio
+14,13,Les Gobelins
+14,14,Place d'Italie
+14,15,Corvisart
+15,0,Denfert
+15,1,Cour Saint-Emilion
+15,2,Porte d'Orleans
+15,3,Bibliotheque François Mitterrand
+15,4,Mouton-Duvernet
+15,5,Alesia
+15,6,Olympiades
+15,8,Glaciere
+15,9,Saint-Jacques
+15,10,Raspail
+15,13,Denfert-Rochereau
+15,14,Denfert-Rochereau
+16,0,Felix Faure
+16,1,Falguiere
+16,2,Pasteur
+16,3,Volontaires
+16,4,Vaugirard
+16,5,Convention
+16,6,Porte de Versailles
+16,9,Balard
+16,10,Lourmel
+16,11,Boucicaut
+16,12,Felix Faure
+16,13,Charles Michels
+16,14,Javel - Andre Citroen
+17,0,Passy
+17,2,Porte Dauphine
+17,4,La Motte-Picquet - Grenelle
+17,5,Commerce
+17,6,Avenue emile Zola
+17,7,Dupleix
+17,8,Passy
+17,9,Ranelagh
+17,11,La Muette
+17,13,Rue de la Pompe
+17,14,Boissiere
+17,15,Trocadero
+18,0,Etoile
+18,1,Iena
+18,3,Alma - Marceau
+18,4,Miromesnil
+18,5,Saint-Philippe du Roule
+18,7,Franklin D. Roosevelt
+18,8,George V
+18,9,Kleber
+18,10,Victor Hugo
+18,11,Argentine
+18,12,Charles de Gaulle - Itoile
+18,14,Ternes
+18,15,Courcelles
+19,0,Clichy - Saint Ouen
+19,1,Mairie de Clichy
+19,2,Gabriel Peri
+19,3,Les Agnettes
+19,4,Asnieres - Gennevilliers - Les Courtilles
+19,9,La Chapelle
+19,10,Garibaldi
+19,11,Mairie de Saint-Ouen
+19,13,Carrefour Pleyel
+19,14,Saint-Denis - Porte de Paris
+19,15,Basilique de Saint-Denis
+20,0,Montmartre
+20,1,Porte de Clignancourt
+20,6,Porte de la Chapelle
+20,7,Marx Dormoy
+20,9,Marcadet - Poissonniers
+20,10,Simplon
+20,11,Jules Joffrin
+20,12,Lamarck - Caulaincourt
+21,0,Lafayette
+21,1,Chaussee d'Antin - La Fayette
+21,2,Le Peletier
+21,3,Cadet
+21,4,Château Rouge
+21,7,Barbes - Rochechouart
+21,8,Gare du Nord
+21,9,Gare de l'Est
+21,10,Poissonniere
+21,11,Château-Landon
+22,0,Buttes Chaumont
+22,1,Porte de Pantin
+22,2,Ourcq
+22,4,Corentin Cariou
+22,6,Crimee
+22,8,Riquet
+22,9,La Chapelle
+22,10,Louis Blanc
+22,11,Stalingrad
+22,12,Jaures
+22,13,Laumiere
+22,14,Bolivar
+22,15,Colonel Fabien
+23,0,Belleville
+23,2,Porte des Lilas
+23,3,Mairie des Lilas
+23,4,Porte de Bagnolet
+23,5,Gallieni
+23,8,Place des Fetes
+23,9,Botzaris
+23,10,Danube
+23,11,Pre Saint-Gervais
+23,13,Buttes Chaumont
+23,14,Jourdain
+23,15,Telegraphe
+24,0,Pere Lachaise
+24,1,Voltaire
+24,2,Charonne
+24,4,Pere Lachaise
+24,5,Menilmontant
+24,6,Rue Saint-Maur
+24,7,Philippe Auguste
+24,8,Saint-Fargeau
+24,9,Pelleport
+24,10,Gambetta
+24,12,Belleville
+24,13,Couronnes
+24,14,Pyrenees
+25,0,Charenton
+25,2,Croix de Chavaux
+25,3,Mairie de Montreuil
+25,4,Maisons-Alfort - Les Juilliottes
+25,5,Creteil - L'echat
+25,6,Creteil - Universite
+25,7,Creteil - Prefecture
+25,8,Saint-Mande
+25,10,Berault
+25,11,Château de Vincennes
+25,12,Liberte
+25,13,Charenton - ecoles
+25,14,Ecole veterinaire de Maisons-Alfort
+25,15,Maisons-Alfort - Stade
+26,0,Ivry - Villejuif
+26,3,Porte d'Ivry
+26,4,Pierre et Marie Curie
+26,5,Mairie d'Ivry
+26,6,Le Kremlin-Bicetre
+26,7,Villejuif - Leo Lagrange
+26,8,Villejuif - Paul Vaillant-Couturier
+26,9,Villejuif - Louis Aragon
+26,11,Villejuif - Institut Gustave Roussy
+27,0,Vanves
+27,2,Porte de Vanves
+27,7,Malakoff - Plateau de Vanves
+27,8,Malakoff - Rue etienne Dolet
+27,9,Châtillon - Montrouge
+28,0,Issy
+28,2,Corentin Celton
+28,3,Mairie d'Issy
+28,8,Marcel Sembat
+28,9,Billancourt
+28,10,Pont de Sevres
+29,0,Levallois
+29,4,Boulogne - Jean Jaures
+29,5,Boulogne - Pont de Saint-Cloud
+29,8,Les Sablons
+29,9,Pont de Neuilly
+29,10,Esplanade de la Defense
+29,11,La Defense
+29,12,Porte de Champerret
+29,13,Louise Michel
+29,14,Anatole France
+29,15,Pont de Levallois - Becon
+30,0,Pereire
+30,1,Porte Maillot
+30,4,Wagram
+30,5,Pereire
+30,8,Brochant
+30,9,Porte de Clichy
+30,12,Guy Moquet
+30,13,Porte de Saint-Ouen
+31,0,Pigalle
+31,2,Funiculaire de Montmartre (station inferieure)
+31,3,Funiculaire de Montmartre (station superieure)
+31,4,Anvers
+31,5,Abbesses
+31,6,Pigalle
+31,7,Blanche
+31,8,Trinite - d'Estienne d'Orves
+31,9,Notre-Dame-de-Lorette
+31,10,Saint-Georges
+31,12,Rome
+31,13,Place de Clichy
+31,14,La Fourche

+ 459 - 0
files/navigo/sncf_stations.txt

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

+ 207 - 60
scenes/metroflip_scene_calypso.c

@@ -120,7 +120,8 @@ void update_page_info(void* context, FuriString* parsed_data) {
         case CALYPSO_CARD_OPUS: {
             furi_string_cat_printf(parsed_data, "\e#Opus %u:\n", ctx->card->card_number);
             furi_string_cat_printf(parsed_data, "\e#Environment:\n");
-            show_opus_environment_info(&ctx->card->opus->environment, parsed_data);
+            show_opus_environment_info(
+                &ctx->card->opus->environment, &ctx->card->opus->holder, parsed_data);
             break;
         }
         case CALYPSO_CARD_RAVKAV: {
@@ -172,44 +173,48 @@ void update_page_info(void* context, FuriString* parsed_data) {
         }
         }
     } else if(ctx->page_id >= 5) {
-        furi_string_cat_printf(parsed_data, "\e#Event %d:\n", ctx->page_id - 4);
-        switch(ctx->card->card_type) {
-        case CALYPSO_CARD_NAVIGO: {
-            show_navigo_event_info(
-                &ctx->card->navigo->events[ctx->page_id - 5],
-                ctx->card->navigo->contracts,
-                parsed_data);
-            break;
-        }
-        case CALYPSO_CARD_OPUS: {
-            show_opus_event_info(
-                &ctx->card->opus->events[ctx->page_id - 5],
-                ctx->card->opus->contracts,
-                parsed_data);
-            break;
-        }
-        case CALYPSO_CARD_RAVKAV: {
-            show_ravkav_event_info(&ctx->card->ravkav->events[ctx->page_id - 5], parsed_data);
-            break;
-        }
-        default: {
-            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;
-        }
+        if(ctx->page_id - 5 < ctx->card->events_count) {
+            furi_string_cat_printf(parsed_data, "\e#Event %d:\n", ctx->page_id - 4);
+            switch(ctx->card->card_type) {
+            case CALYPSO_CARD_NAVIGO: {
+                show_navigo_event_info(
+                    &ctx->card->navigo->events[ctx->page_id - 5],
+                    ctx->card->navigo->contracts,
+                    parsed_data);
+                break;
+            }
+            case CALYPSO_CARD_OPUS: {
+                show_opus_event_info(
+                    &ctx->card->opus->events[ctx->page_id - 5],
+                    ctx->card->opus->contracts,
+                    parsed_data);
+                break;
+            }
+            case CALYPSO_CARD_RAVKAV: {
+                show_ravkav_event_info(&ctx->card->ravkav->events[ctx->page_id - 5], parsed_data);
+                break;
+            }
+            default: {
+                break;
+            }
+            }
+        } else {
+            furi_string_cat_printf(
+                parsed_data, "\e#Special Event %d:\n", ctx->page_id - ctx->card->events_count - 4);
+            switch(ctx->card->card_type) {
+            case CALYPSO_CARD_NAVIGO: {
+                show_navigo_special_event_info(
+                    &ctx->card->navigo->special_events[ctx->page_id - ctx->card->events_count - 5],
+                    parsed_data);
+                break;
+            }
+            case CALYPSO_CARD_OPUS: {
+                break;
+            }
+            default: {
+                break;
+            }
+            }
         }
     }
 }
@@ -1296,6 +1301,16 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                     card->opus->environment.app_version =
                         bit_slice_to_dec(environment_bit_representation, start, end);
 
+                    // EnvApplicationIssuerId
+                    env_key = "EnvApplicationIssuerId";
+                    positionOffset = get_calypso_node_offset(
+                        environment_bit_representation, env_key, OpusEnvHolderStructure);
+                    start = positionOffset,
+                    end = positionOffset + get_calypso_node_size(env_key, OpusEnvHolderStructure) -
+                          1;
+                    card->opus->environment.issuer_id =
+                        bit_slice_to_dec(environment_bit_representation, start, end);
+
                     // EnvApplicationValidityEndDate
                     env_key = "EnvApplicationValidityEndDate";
                     positionOffset = get_calypso_node_offset(
@@ -1310,26 +1325,46 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                     datetime_timestamp_to_datetime(
                         end_validity_timestamp, &card->opus->environment.end_dt);
 
-                    // HolderDataCardStatus
-                    env_key = "HolderDataCardStatus";
+                    // EnvDataCardStatus
+                    env_key = "EnvDataCardStatus";
                     positionOffset = get_calypso_node_offset(
                         environment_bit_representation, env_key, OpusEnvHolderStructure);
                     start = positionOffset,
                     end = positionOffset + get_calypso_node_size(env_key, OpusEnvHolderStructure) -
                           1;
-                    card->opus->holder.card_status =
+                    card->opus->environment.card_status =
                         bit_slice_to_dec(environment_bit_representation, start, end);
 
-                    // HolderDataCommercialID
-                    env_key = "HolderDataCommercialID";
+                    // EnvData_CardUtilisation
+                    env_key = "EnvData_CardUtilisation";
                     positionOffset = get_calypso_node_offset(
                         environment_bit_representation, env_key, OpusEnvHolderStructure);
                     start = positionOffset,
                     end = positionOffset + get_calypso_node_size(env_key, OpusEnvHolderStructure) -
                           1;
-                    card->opus->holder.commercial_id =
+                    card->opus->environment.card_utilisation =
                         bit_slice_to_dec(environment_bit_representation, start, end);
 
+                    // HolderBirthDate
+                    env_key = "HolderBirthDate";
+                    positionOffset = get_calypso_node_offset(
+                        environment_bit_representation, env_key, OpusEnvHolderStructure);
+                    start = positionOffset, end = positionOffset + 3;
+                    card->opus->holder.birth_date.year =
+                        bit_slice_to_dec(environment_bit_representation, start, end) * 1000 +
+                        bit_slice_to_dec(environment_bit_representation, start + 4, end + 4) *
+                            100 +
+                        bit_slice_to_dec(environment_bit_representation, start + 8, end + 8) * 10 +
+                        bit_slice_to_dec(environment_bit_representation, start + 12, end + 12);
+                    start += 16, end += 16;
+                    card->opus->holder.birth_date.month =
+                        bit_slice_to_dec(environment_bit_representation, start, end) * 10 +
+                        bit_slice_to_dec(environment_bit_representation, start + 4, end + 4);
+                    start += 8, end += 8;
+                    card->opus->holder.birth_date.day =
+                        bit_slice_to_dec(environment_bit_representation, start, end) * 10 +
+                        bit_slice_to_dec(environment_bit_representation, start + 4, end + 4);
+
                     // Free the calypso structure
                     free_calypso_structure(OpusEnvHolderStructure);
 
@@ -1384,7 +1419,7 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                         if(bit_slice_to_dec(
                                bit_representation,
                                0,
-                               OpusContractStructure->container->elements[1].bitmap->size - 1) ==
+                               OpusContractStructure->container->elements[0].bitmap->size - 1) ==
                            0) {
                             break;
                         }
@@ -1420,8 +1455,8 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                                 bit_slice_to_dec(bit_representation, start, end);
                         }
 
-                        // ContractStartDate
-                        contract_key = "ContractStartDate";
+                        // ContractValidityStartDate
+                        contract_key = "ContractValidityStartDate";
                         if(is_calypso_node_present(
                                bit_representation, contract_key, OpusContractStructure)) {
                             int positionOffset = get_calypso_node_offset(
@@ -1439,8 +1474,8 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                                 &card->opus->contracts[i - 1].start_date);
                         }
 
-                        // ContractEndDate
-                        contract_key = "ContractEndDate";
+                        // ContractValidityEndDate
+                        contract_key = "ContractValidityEndDate";
                         if(is_calypso_node_present(
                                bit_representation, contract_key, OpusContractStructure)) {
                             int positionOffset = get_calypso_node_offset(
@@ -1457,8 +1492,8 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                                 end_validity_timestamp, &card->opus->contracts[i - 1].end_date);
                         }
 
-                        // ContractStatus
-                        contract_key = "ContractStatus";
+                        // ContractDataSaleAgent
+                        contract_key = "ContractDataSaleAgent";
                         if(is_calypso_node_present(
                                bit_representation, contract_key, OpusContractStructure)) {
                             int positionOffset = get_calypso_node_offset(
@@ -1467,24 +1502,31 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                                 end = positionOffset +
                                       get_calypso_node_size(contract_key, OpusContractStructure) -
                                       1;
-                            card->opus->contracts[i - 1].status =
+                            card->opus->contracts[i - 1].sale_agent =
                                 bit_slice_to_dec(bit_representation, start, end);
                         }
 
-                        // ContractSaleDate + ContractSaleTime
-                        contract_key = "ContractSaleDate";
+                        // ContractDataSaleDate + ContractDataSaleTime
+                        contract_key = "ContractDataSaleDate";
                         int positionOffset = get_calypso_node_offset(
                             bit_representation, contract_key, OpusContractStructure);
+                        FURI_LOG_I(TAG, "ContractDataSaleDate positionOffset: %d", positionOffset);
                         int start = positionOffset,
                             end = positionOffset +
                                   get_calypso_node_size(contract_key, OpusContractStructure) - 1;
+                        FURI_LOG_I(
+                            TAG,
+                            "ContractDataSaleDate: %d",
+                            bit_slice_to_dec(bit_representation, start, end));
                         uint64_t sale_date_timestamp =
-                            (bit_slice_to_dec(bit_representation, start, end) + (float)epoch) +
+                            ((bit_slice_to_dec(bit_representation, start, end) * 24 * 3600) +
+                             (float)epoch) +
                             3600;
+                        ;
                         datetime_timestamp_to_datetime(
                             sale_date_timestamp, &card->opus->contracts[i - 1].sale_date);
 
-                        contract_key = "ContractSaleTime";
+                        contract_key = "ContractDataSaleTime";
                         positionOffset = get_calypso_node_offset(
                             bit_representation, contract_key, OpusContractStructure);
                         start = positionOffset,
@@ -1496,6 +1538,34 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                             ((decimal_value * 60) % 3600) / 60;
                         card->opus->contracts[i - 1].sale_date.second =
                             ((decimal_value * 60) % 3600) % 60;
+
+                        // ContractDataInhibition
+                        contract_key = "ContractDataInhibition";
+                        if(is_calypso_node_present(
+                               bit_representation, contract_key, OpusContractStructure)) {
+                            int positionOffset = get_calypso_node_offset(
+                                bit_representation, contract_key, OpusContractStructure);
+                            int start = positionOffset,
+                                end = positionOffset +
+                                      get_calypso_node_size(contract_key, OpusContractStructure) -
+                                      1;
+                            card->opus->contracts[i - 1].inhibition =
+                                bit_slice_to_dec(bit_representation, start, end);
+                        }
+
+                        // ContractDataUsed
+                        contract_key = "ContractDataUsed";
+                        if(is_calypso_node_present(
+                               bit_representation, contract_key, OpusContractStructure)) {
+                            int positionOffset = get_calypso_node_offset(
+                                bit_representation, contract_key, OpusContractStructure);
+                            int start = positionOffset,
+                                end = positionOffset +
+                                      get_calypso_node_size(contract_key, OpusContractStructure) -
+                                      1;
+                            card->opus->contracts[i - 1].used =
+                                bit_slice_to_dec(bit_representation, start, end);
+                        }
                     }
 
                     // Free the calypso structure
@@ -1543,8 +1613,21 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                                 event_bit_representation, bits, sizeof(event_bit_representation));
                         }
 
+                        // EventResult
+                        const char* event_key = "EventResult";
+                        if(is_calypso_node_present(
+                               event_bit_representation, event_key, OpusEventStructure)) {
+                            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;
+                            card->opus->events[i - 1].result =
+                                bit_slice_to_dec(event_bit_representation, start, end);
+                        }
+
                         // EventServiceProvider
-                        const char* event_key = "EventServiceProvider";
+                        event_key = "EventServiceProvider";
                         if(is_calypso_node_present(
                                event_bit_representation, event_key, OpusEventStructure)) {
                             int positionOffset = get_calypso_node_offset(
@@ -1556,6 +1639,19 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                                 bit_slice_to_dec(event_bit_representation, start, end);
                         }
 
+                        // EventLocationId
+                        event_key = "EventLocationId";
+                        if(is_calypso_node_present(
+                               event_bit_representation, event_key, OpusEventStructure)) {
+                            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;
+                            card->opus->events[i - 1].location_id =
+                                bit_slice_to_dec(event_bit_representation, start, end);
+                        }
+
                         // EventRouteNumber
                         event_key = "EventRouteNumber";
                         if(is_calypso_node_present(
@@ -1567,7 +1663,6 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                                       get_calypso_node_size(event_key, OpusEventStructure) - 1;
                             card->opus->events[i - 1].route_number =
                                 bit_slice_to_dec(event_bit_representation, start, end);
-                            card->opus->events[i - 1].route_number_available = true;
                         }
 
                         // EventContractPointer
@@ -1581,12 +1676,37 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                                       get_calypso_node_size(event_key, OpusEventStructure) - 1;
                             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++;
                             }
                         }
 
+                        // EventDataSimulation
+                        event_key = "EventDataSimulation";
+                        if(is_calypso_node_present(
+                               event_bit_representation, event_key, OpusEventStructure)) {
+                            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;
+                            card->opus->events[i - 1].simulation =
+                                bit_slice_to_dec(event_bit_representation, start, end);
+                        }
+
+                        // EventDataRouteDirection
+                        event_key = "EventDataRouteDirection";
+                        if(is_calypso_node_present(
+                               event_bit_representation, event_key, OpusEventStructure)) {
+                            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;
+                            card->opus->events[i - 1].route_direction =
+                                bit_slice_to_dec(event_bit_representation, start, end);
+                        }
+
                         // EventDateStamp
                         event_key = "EventDateStamp";
                         int positionOffset = get_calypso_node_offset(
@@ -1610,6 +1730,33 @@ static NfcCommand metroflip_scene_calypso_poller_callback(NfcGenericEvent event,
                         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;
+
+                        // EventDataDateFirstStamp
+                        event_key = "EventDataDateFirstStamp";
+                        positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, OpusEventStructure);
+                        start = positionOffset,
+                        end = positionOffset +
+                              get_calypso_node_size(event_key, OpusEventStructure) - 1;
+                        decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
+                        uint64_t first_date_timestamp = (decimal_value * 24 * 3600) + epoch + 3600;
+                        datetime_timestamp_to_datetime(
+                            first_date_timestamp, &card->opus->events[i - 1].first_stamp_date);
+
+                        // EventDataTimeFirstStamp
+                        event_key = "EventDataTimeFirstStamp";
+                        positionOffset = get_calypso_node_offset(
+                            event_bit_representation, event_key, OpusEventStructure);
+                        start = positionOffset,
+                        end = positionOffset +
+                              get_calypso_node_size(event_key, OpusEventStructure) - 1;
+                        decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
+                        card->opus->events[i - 1].first_stamp_date.hour =
+                            (decimal_value * 60) / 3600;
+                        card->opus->events[i - 1].first_stamp_date.minute =
+                            ((decimal_value * 60) % 3600) / 60;
+                        card->opus->events[i - 1].first_stamp_date.second =
+                            ((decimal_value * 60) % 3600) % 60;
                     }
 
                     // Free the calypso structure