Преглед изворни кода

Support for Calypso type detection + Add basic Opus support (WIP)

DocSystem пре 1 година
родитељ
комит
328ff2a961

+ 31 - 0
api/calypso/calypso_i.h

@@ -0,0 +1,31 @@
+#include "transit/navigo_i.h"
+#include "transit/opus_i.h"
+#include <furi.h>
+
+#ifndef CALYPSO_I_H
+#define CALYPSO_I_H
+
+typedef enum {
+    CALYPSO_CARD_NAVIGO,
+    CALYPSO_CARD_OPUS,
+    CALYPSO_CARD_UNKNOWN
+} CALYPSO_CARD_TYPE;
+
+typedef struct {
+    NavigoCardData* navigo;
+    OpusCardData* opus;
+
+    CALYPSO_CARD_TYPE card_type;
+    unsigned int card_number;
+
+    int contracts_count;
+} CalypsoCardData;
+
+typedef struct {
+    CalypsoCardData* card;
+    int page_id;
+    // mutex
+    FuriMutex* mutex;
+} CalypsoContext;
+
+#endif // CALYPSO_I_H

+ 156 - 76
api/calypso/calypso_util.c

@@ -34,23 +34,46 @@ CalypsoElement make_calypso_bitmap_element(const char* key, int size, CalypsoEle
     return bitmap_element;
 }
 
+CalypsoElement
+    make_calypso_container_element(const char* key, int size, CalypsoElement* elements) {
+    CalypsoElement container_element = {};
+
+    container_element.type = CALYPSO_ELEMENT_TYPE_CONTAINER;
+    container_element.container = malloc(sizeof(CalypsoContainerElement));
+    container_element.container->size = size;
+    container_element.container->elements = malloc(size * sizeof(CalypsoElement));
+    for(int i = 0; i < size; i++) {
+        container_element.container->elements[i] = elements[i];
+    }
+    strncpy(container_element.container->key, key, 36);
+
+    return container_element;
+}
+
 void free_calypso_element(CalypsoElement* element) {
     if(element->type == CALYPSO_ELEMENT_TYPE_FINAL) {
         free(element->final);
-    } else {
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_BITMAP) {
         for(int i = 0; i < element->bitmap->size; i++) {
             free_calypso_element(&element->bitmap->elements[i]);
         }
         free(element->bitmap->elements);
         free(element->bitmap);
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_CONTAINER) {
+        for(int i = 0; i < element->container->size; i++) {
+            free_calypso_element(&element->container->elements[i]);
+        }
+        free(element->container->elements);
+        free(element->container);
     }
 }
 
 void free_calypso_structure(CalypsoApp* structure) {
-    for(int i = 0; i < structure->elements_size; i++) {
-        free_calypso_element(&structure->elements[i]);
+    for(int i = 0; i < structure->container->size; i++) {
+        free_calypso_element(&structure->container->elements[i]);
     }
-    free(structure->elements);
+    free(structure->container->elements);
+    free(structure->container);
     free(structure);
 }
 
@@ -118,25 +141,25 @@ bool is_calypso_subnode_present(
 
 bool is_calypso_node_present(const char* binary_string, const char* key, CalypsoApp* structure) {
     int offset = 0;
-    for(int i = 0; i < structure->elements_size; i++) {
-        if(structure->elements[i].type == CALYPSO_ELEMENT_TYPE_FINAL) {
-            if(strcmp(structure->elements[i].final->key, key) == 0) {
+    for(int i = 0; i < structure->container->size; i++) {
+        if(structure->container->elements[i].type == CALYPSO_ELEMENT_TYPE_FINAL) {
+            if(strcmp(structure->container->elements[i].final->key, key) == 0) {
                 return true;
             }
-            offset += structure->elements[i].final->size;
+            offset += structure->container->elements[i].final->size;
         } else {
-            if(strcmp(structure->elements[i].bitmap->key, key) == 0) {
+            if(strcmp(structure->container->elements[i].bitmap->key, key) == 0) {
                 return true;
             }
-            int sub_binary_string_size = structure->elements[i].bitmap->size;
+            int sub_binary_string_size = structure->container->elements[i].bitmap->size;
             char bit_slice[sub_binary_string_size + 1];
             strncpy(bit_slice, binary_string, sub_binary_string_size);
             bit_slice[sub_binary_string_size] = '\0';
             if(is_calypso_subnode_present(
-                   binary_string + offset, key, structure->elements[i].bitmap)) {
+                   binary_string + offset, key, structure->container->elements[i].bitmap)) {
                 return true;
             }
-            offset += structure->elements[i].bitmap->size;
+            offset += structure->container->elements[i].bitmap->size;
         }
     }
     return false;
@@ -145,68 +168,72 @@ bool is_calypso_node_present(const char* binary_string, const char* key, Calypso
 int get_calypso_subnode_offset(
     const char* binary_string,
     const char* key,
-    CalypsoBitmapElement* bitmap,
+    CalypsoElement* elem,
     bool* found) {
-    char bit_slice[bitmap->size + 1];
-    strncpy(bit_slice, binary_string, bitmap->size);
-    bit_slice[bitmap->size] = '\0';
+    // recursive function to get the offset of a subnode in a calypso binary string
+    if(elem->type == CALYPSO_ELEMENT_TYPE_FINAL) {
+        if(strcmp(elem->final->key, key) == 0) {
+            *found = true;
+            return 0;
+        }
+        return elem->final->size;
+    } else if(elem->type == CALYPSO_ELEMENT_TYPE_BITMAP) {
+        CalypsoBitmapElement* bitmap = elem->bitmap;
 
-    int count = 0;
-    int* positions = get_bit_positions(bit_slice, &count);
+        char bit_slice[bitmap->size + 1];
+        strncpy(bit_slice, binary_string, bitmap->size);
+        bit_slice[bitmap->size] = '\0';
 
-    int count_offset = bitmap->size;
-    for(int i = 0; i < count; i++) {
-        CalypsoElement element = bitmap->elements[positions[i]];
-        if(element.type == CALYPSO_ELEMENT_TYPE_FINAL) {
-            if(strcmp(element.final->key, key) == 0) {
+        int count = 0;
+        int* positions = get_bit_positions(bit_slice, &count);
+        bool f = false;
+
+        int count_offset = bitmap->size;
+        for(int i = 0; i < count; i++) {
+            CalypsoElement element = bitmap->elements[positions[i]];
+            count_offset +=
+                get_calypso_subnode_offset(binary_string + count_offset, key, &element, &f);
+            if(f) {
                 *found = true;
                 free(positions);
                 return count_offset;
             }
-            count_offset += element.final->size;
-        } else {
-            if(strcmp(element.bitmap->key, key) == 0) {
+        }
+
+        free(positions);
+        return count_offset;
+    } else if(elem->type == CALYPSO_ELEMENT_TYPE_CONTAINER) {
+        // same as bitmap but without bitmap at the beginning
+        CalypsoContainerElement* container = elem->container;
+
+        int count_offset = 0;
+        bool f = false;
+        for(int i = 0; i < container->size; i++) {
+            CalypsoElement element = container->elements[i];
+            count_offset +=
+                get_calypso_subnode_offset(binary_string + count_offset, key, &element, &f);
+            if(f) {
                 *found = true;
-                free(positions);
-                return count_offset;
-            }
-            count_offset += get_calypso_subnode_offset(
-                binary_string + count_offset, key, element.bitmap, found);
-            if(*found) {
-                free(positions);
                 return count_offset;
             }
         }
+
+        return count_offset;
     }
-    free(positions);
-    return count_offset;
+    return 0;
 }
 
 int get_calypso_node_offset(const char* binary_string, const char* key, CalypsoApp* structure) {
-    int count = 0;
-    bool found = false;
-    for(int i = 0; i < structure->elements_size; i++) {
-        if(structure->elements[i].type == CALYPSO_ELEMENT_TYPE_FINAL) {
-            if(strcmp(structure->elements[i].final->key, key) == 0) {
-                return count;
-            }
-            count += structure->elements[i].final->size;
-        } else {
-            if(strcmp(structure->elements[i].bitmap->key, key) == 0) {
-                return count;
-            }
-            int sub_binary_string_size = structure->elements[i].bitmap->size;
-            char bit_slice[sub_binary_string_size + 1];
-            strncpy(bit_slice, binary_string + count, sub_binary_string_size);
-            bit_slice[sub_binary_string_size] = '\0';
-            count += get_calypso_subnode_offset(
-                binary_string + count, key, structure->elements[i].bitmap, &found);
-            if(found) {
-                return count;
-            }
-        }
+    CalypsoElement* element = malloc(sizeof(CalypsoElement));
+    element->type = CALYPSO_ELEMENT_TYPE_CONTAINER;
+    element->container = structure->container;
+    bool found;
+    int offset = get_calypso_subnode_offset(binary_string, key, element, &found);
+    if(!found) {
+        FURI_LOG_E("Metroflip:Scene:Calypso", "Key %s not found in calypso structure", key);
     }
-    return 0;
+    free(element);
+    return offset;
 }
 
 int get_calypso_subnode_size(const char* key, CalypsoElement* element) {
@@ -214,7 +241,7 @@ int get_calypso_subnode_size(const char* key, CalypsoElement* element) {
         if(strcmp(element->final->key, key) == 0) {
             return element->final->size;
         }
-    } else {
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_BITMAP) {
         if(strcmp(element->bitmap->key, key) == 0) {
             return element->bitmap->size;
         }
@@ -224,28 +251,81 @@ int get_calypso_subnode_size(const char* key, CalypsoElement* element) {
                 return size;
             }
         }
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_CONTAINER) {
+        if(strcmp(element->container->key, key) == 0) {
+            return element->container->size;
+        }
+        for(int i = 0; i < element->container->size; i++) {
+            int size = get_calypso_subnode_size(key, &element->container->elements[i]);
+            if(size != 0) {
+                return size;
+            }
+        }
     }
     return 0;
 }
 
 int get_calypso_node_size(const char* key, CalypsoApp* structure) {
-    for(int i = 0; i < structure->elements_size; i++) {
-        if(structure->elements[i].type == CALYPSO_ELEMENT_TYPE_FINAL) {
-            if(strcmp(structure->elements[i].final->key, key) == 0) {
-                return structure->elements[i].final->size;
-            }
-        } else {
-            if(strcmp(structure->elements[i].bitmap->key, key) == 0) {
-                return structure->elements[i].bitmap->size;
-            }
-            for(int j = 0; j < structure->elements[i].bitmap->size; j++) {
-                int size =
-                    get_calypso_subnode_size(key, &structure->elements[i].bitmap->elements[j]);
-                if(size != 0) {
-                    return size;
-                }
-            }
+    CalypsoElement* element = malloc(sizeof(CalypsoElement));
+    element->type = CALYPSO_ELEMENT_TYPE_CONTAINER;
+    element->container = structure->container;
+    int count = get_calypso_subnode_size(key, element);
+    free(element);
+    return count;
+}
+
+CALYPSO_CARD_TYPE guess_card_type(int country_num, int network_num) {
+    switch(country_num) {
+    case 250:
+        switch(network_num) {
+        case 901:
+            return CALYPSO_CARD_NAVIGO;
+        default:
+            return CALYPSO_CARD_UNKNOWN;
+        }
+    case 124:
+        switch(network_num) {
+        case 1:
+            return CALYPSO_CARD_OPUS;
+        default:
+            return CALYPSO_CARD_UNKNOWN;
         }
+    default:
+        return CALYPSO_CARD_UNKNOWN;
+    }
+}
+
+const char* get_country_string(int country_num) {
+    switch(country_num) {
+    case 250:
+        return "France";
+    case 124:
+        return "Canada";
+    default: {
+        char* country = malloc(4 * sizeof(char));
+        snprintf(country, 4, "%d", country_num);
+        return country;
+    }
+    }
+}
+
+const char* get_network_string(int country_num, int network_num) {
+    switch(country_num) {
+    case 250:
+        switch(network_num) {
+        case 901:
+            return "IDFM";
+        default:
+            return "Unknown";
+        }
+    case 124:
+        switch(network_num) {
+        case 1:
+            return "Opus";
+        default:
+            return "Unknown";
+        }
+    default:
+        return "Unknown";
     }
-    return 0;
 }

+ 24 - 2
api/calypso/calypso_util.h

@@ -1,10 +1,13 @@
 #include <stdbool.h>
+#include "calypso_i.h"
 
 #ifndef CALYPSO_UTIL_H
 #define CALYPSO_UTIL_H
 
 typedef enum {
     CALYPSO_APP_CONTRACT,
+    CALYPSO_APP_EVENT,
+    CALYPSO_APP_ENV_HOLDER,
 } CalypsoAppType;
 
 typedef enum {
@@ -20,21 +23,25 @@ typedef enum {
     CALYPSO_FINAL_TYPE_NETWORK_ID,
     CALYPSO_FINAL_TYPE_TRANSPORT_TYPE,
     CALYPSO_FINAL_TYPE_CARD_STATUS,
+    CALYPSO_FINAL_TYPE_STRING,
 } CalypsoFinalType;
 
 typedef enum {
+    CALYPSO_ELEMENT_TYPE_CONTAINER,
     CALYPSO_ELEMENT_TYPE_BITMAP,
     CALYPSO_ELEMENT_TYPE_FINAL
 } CalypsoElementType;
 
 typedef struct CalypsoFinalElement_t CalypsoFinalElement;
 typedef struct CalypsoBitmapElement_t CalypsoBitmapElement;
+typedef struct CalypsoContainerElement_t CalypsoContainerElement;
 
 typedef struct {
     CalypsoElementType type;
     union {
         CalypsoFinalElement* final;
         CalypsoBitmapElement* bitmap;
+        CalypsoContainerElement* container;
     };
 } CalypsoElement;
 
@@ -51,10 +58,15 @@ struct CalypsoBitmapElement_t {
     CalypsoElement* elements;
 };
 
+struct CalypsoContainerElement_t {
+    char key[36];
+    int size;
+    CalypsoElement* elements;
+};
+
 typedef struct {
     CalypsoAppType type;
-    CalypsoElement* elements;
-    int elements_size;
+    CalypsoContainerElement* container;
 } CalypsoApp;
 
 CalypsoElement make_calypso_final_element(
@@ -65,6 +77,8 @@ CalypsoElement make_calypso_final_element(
 
 CalypsoElement make_calypso_bitmap_element(const char* key, int size, CalypsoElement* elements);
 
+CalypsoElement make_calypso_container_element(const char* key, int size, CalypsoElement* elements);
+
 void free_calypso_structure(CalypsoApp* structure);
 
 int* get_bit_positions(const char* binary_string, int* count);
@@ -77,4 +91,12 @@ int get_calypso_node_offset(const char* binary_string, const char* key, CalypsoA
 
 int get_calypso_node_size(const char* key, CalypsoApp* structure);
 
+// Calypso known Card types
+
+CALYPSO_CARD_TYPE guess_card_type(int country_num, int network_num);
+
+const char* get_country_string(int country_num);
+
+const char* get_network_string(int country_num, int network_num);
+
 #endif // CALYPSO_UTIL_H

+ 187 - 16
api/calypso/cards/intercode.c

@@ -2,18 +2,20 @@
 #include "intercode.h"
 
 CalypsoApp* get_intercode_contract_structure() {
-    CalypsoApp* NavigoContractStructure = malloc(sizeof(CalypsoApp));
-    if(!NavigoContractStructure) {
+    CalypsoApp* IntercodeContractStructure = malloc(sizeof(CalypsoApp));
+    if(!IntercodeContractStructure) {
         return NULL;
     }
 
     int app_elements_count = 1;
 
-    NavigoContractStructure->type = CALYPSO_APP_CONTRACT;
-    NavigoContractStructure->elements = malloc(app_elements_count * sizeof(CalypsoElement));
-    NavigoContractStructure->elements_size = app_elements_count;
+    IntercodeContractStructure->type = CALYPSO_APP_CONTRACT;
+    IntercodeContractStructure->container = malloc(sizeof(CalypsoContainerElement));
+    IntercodeContractStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    IntercodeContractStructure->container->size = app_elements_count;
 
-    NavigoContractStructure->elements[0] = make_calypso_bitmap_element(
+    IntercodeContractStructure->container->elements[0] = make_calypso_bitmap_element(
         "Contract",
         20,
         (CalypsoElement[]){
@@ -243,28 +245,30 @@ CalypsoApp* get_intercode_contract_structure() {
                 "ContractData(0..255)", 0, "Données complémentaires", CALYPSO_FINAL_TYPE_UNKNOWN),
         });
 
-    return NavigoContractStructure;
+    return IntercodeContractStructure;
 }
 
 CalypsoApp* get_intercode_event_structure() {
-    CalypsoApp* NavigoEventStructure = malloc(sizeof(CalypsoApp));
-    if(!NavigoEventStructure) {
+    CalypsoApp* IntercodeEventStructure = malloc(sizeof(CalypsoApp));
+    if(!IntercodeEventStructure) {
         return NULL;
     }
 
     int app_elements_count = 3;
 
-    NavigoEventStructure->type = CALYPSO_APP_CONTRACT;
-    NavigoEventStructure->elements = malloc(app_elements_count * sizeof(CalypsoElement));
-    NavigoEventStructure->elements_size = app_elements_count;
+    IntercodeEventStructure->type = CALYPSO_APP_EVENT;
+    IntercodeEventStructure->container = malloc(sizeof(CalypsoContainerElement));
+    IntercodeEventStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    IntercodeEventStructure->container->size = app_elements_count;
 
-    NavigoEventStructure->elements[0] = make_calypso_final_element(
+    IntercodeEventStructure->container->elements[0] = make_calypso_final_element(
         "EventDateStamp", 14, "Date de l’événement", CALYPSO_FINAL_TYPE_DATE);
 
-    NavigoEventStructure->elements[1] = make_calypso_final_element(
+    IntercodeEventStructure->container->elements[1] = make_calypso_final_element(
         "EventTimeStamp", 11, "Heure de l’événement", CALYPSO_FINAL_TYPE_TIME);
 
-    NavigoEventStructure->elements[2] = make_calypso_bitmap_element(
+    IntercodeEventStructure->container->elements[2] = make_calypso_bitmap_element(
         "EventBitmap",
         28,
         (CalypsoElement[]){
@@ -375,5 +379,172 @@ CalypsoApp* get_intercode_event_structure() {
                 }),
         });
 
-    return NavigoEventStructure;
+    return IntercodeEventStructure;
+}
+
+CalypsoApp* get_intercode_env_holder_structure() {
+    CalypsoApp* IntercodeEnvHolderStructure = malloc(sizeof(CalypsoApp));
+    if(!IntercodeEnvHolderStructure) {
+        return NULL;
+    }
+
+    int app_elements_count = 3;
+
+    IntercodeEnvHolderStructure->type = CALYPSO_APP_ENV_HOLDER;
+    IntercodeEnvHolderStructure->container = malloc(sizeof(CalypsoContainerElement));
+    IntercodeEnvHolderStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    IntercodeEnvHolderStructure->container->size = app_elements_count;
+
+    IntercodeEnvHolderStructure->container->elements[0] = make_calypso_final_element(
+        "EnvApplicationVersionNumber",
+        6,
+        "Numéro de version de l’application Billettique",
+        CALYPSO_FINAL_TYPE_NUMBER);
+    IntercodeEnvHolderStructure->container->elements[1] = make_calypso_bitmap_element(
+        "Env",
+        7,
+        (CalypsoElement[]){
+            make_calypso_final_element(
+                "EnvNetworkId", 24, "Identification du réseau", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_final_element(
+                "EnvApplicationIssuerId",
+                8,
+                "Identification de l’émetteur de l’application",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EnvApplicationValidityEndDate",
+                14,
+                "Date de fin de validité de l’application",
+                CALYPSO_FINAL_TYPE_DATE),
+            make_calypso_final_element(
+                "EnvPayMethod", 11, "Code mode de paiement", CALYPSO_FINAL_TYPE_PAY_METHOD),
+            make_calypso_final_element(
+                "EnvAuthenticator",
+                16,
+                "Code de contrôle de l’intégrité des données",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EnvSelectList",
+                32,
+                "Bitmap de tableau de paramètre multiple",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_container_element(
+                "EnvData",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "EnvDataCardStatus", 1, "Statut de la carte", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EnvData2", 0, "Données complémentaires", CALYPSO_FINAL_TYPE_UNKNOWN),
+                }),
+        });
+    IntercodeEnvHolderStructure->container->elements[2] = make_calypso_bitmap_element(
+        "Holder",
+        8,
+        (CalypsoElement[]){
+            make_calypso_bitmap_element(
+                "HolderName",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "HolderSurname", 85, "Nom du porteur", CALYPSO_FINAL_TYPE_STRING),
+                    make_calypso_final_element(
+                        "HolderForename",
+                        85,
+                        "Prénom de naissance du porteur",
+                        CALYPSO_FINAL_TYPE_STRING),
+                }),
+            make_calypso_bitmap_element(
+                "HolderBirth",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "HolderBirthDate", 32, "Date de naissance", CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderBirthPlace",
+                        115,
+                        "Lieu de naissance (23 caractères)",
+                        CALYPSO_FINAL_TYPE_STRING),
+                }),
+            make_calypso_final_element(
+                "HolderBirthName",
+                85,
+                "Nom de naissance du porteur (17 caractères)",
+                CALYPSO_FINAL_TYPE_STRING),
+            make_calypso_final_element(
+                "HolderIdNumber", 32, "Identifiant Porteur", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_final_element(
+                "HolderCountryAlpha", 24, "Pays du titulaire", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderCompany", 32, "Société du titulaire", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_bitmap_element(
+                "HolderProfiles",
+                4,
+                (CalypsoElement[]){
+                    make_calypso_bitmap_element(
+                        "HolderProfileBitmap",
+                        3,
+                        (CalypsoElement[]){
+                            make_calypso_final_element(
+                                "HolderNetworkId", 24, "Réseau", CALYPSO_FINAL_TYPE_UNKNOWN),
+                            make_calypso_final_element(
+                                "HolderProfileNumber",
+                                8,
+                                "Numéro du statut",
+                                CALYPSO_FINAL_TYPE_NUMBER),
+                            make_calypso_final_element(
+                                "HolderProfileDate",
+                                14,
+                                "Date de fin de validité du statut",
+                                CALYPSO_FINAL_TYPE_DATE),
+                        }),
+                }),
+            make_calypso_bitmap_element(
+                "HolderData",
+                12,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "HolderDataCardStatus", 4, "Type de carte", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataTeleReglement", 4, "Télérèglement", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataResidence", 17, "Ville du domicile", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataCommercialID", 6, "Produit carte", CALYPSO_FINAL_TYPE_NUMBER),
+                    make_calypso_final_element(
+                        "HolderDataWorkPlace", 17, "Lieu de travail", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataStudyPlace", 17, "Lieu d'étude", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataSaleDevice",
+                        16,
+                        "Numéro logique de SAM",
+                        CALYPSO_FINAL_TYPE_NUMBER),
+                    make_calypso_final_element(
+                        "HolderDataAuthenticator", 16, "Signature", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate1",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate2",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate3",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate4",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                }),
+        });
+
+    return IntercodeEnvHolderStructure;
 }

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

@@ -7,4 +7,6 @@ CalypsoApp* get_intercode_contract_structure();
 
 CalypsoApp* get_intercode_event_structure();
 
-#endif // NAVIGO_STRUCTURES_H
+CalypsoApp* get_intercode_env_holder_structure();
+
+#endif // INTERCODE_STRUCTURES_H

+ 245 - 0
api/calypso/cards/opus.c

@@ -0,0 +1,245 @@
+#include <stdlib.h>
+#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;
+
+    OpusContractStructure->type = CALYPSO_APP_CONTRACT;
+    OpusContractStructure->container = malloc(sizeof(CalypsoContainerElement));
+    OpusContractStructure->container->elements =
+        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(
+        "Contract",
+        4,
+        (CalypsoElement[]){
+            make_calypso_final_element(
+                "ContractProvider", 8, "Provider", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element("ContractTariff", 16, "Tariff", CALYPSO_FINAL_TYPE_TARIFF),
+            make_calypso_bitmap_element(
+                "ContractDates",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "ContractStartDate", 14, "Start date", CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "ContractEndDate", 14, "End date", CALYPSO_FINAL_TYPE_DATE),
+                }),
+            make_calypso_bitmap_element(
+                "ContractSaleData",
+                6,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "ContractUnknownB", 17, "Unknown B", CALYPSO_FINAL_TYPE_NUMBER),
+                    make_calypso_final_element(
+                        "ContractSaleDate", 14, "Sale date", CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "ContractSaleTime", 11, "Sale time", CALYPSO_FINAL_TYPE_TIME),
+                    make_calypso_final_element(
+                        "ContractUnknownC", 36, "Unknown C", CALYPSO_FINAL_TYPE_NUMBER),
+                    make_calypso_final_element(
+                        "ContractStatus", 8, "Status", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "ContractUnknownD", 36, "Unknown D", CALYPSO_FINAL_TYPE_NUMBER),
+                }),
+        });
+
+    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) {
+        return NULL;
+    }
+
+    int app_elements_count = 4;
+
+    OpusEventStructure->type = CALYPSO_APP_EVENT;
+    OpusEventStructure->container = malloc(sizeof(CalypsoContainerElement));
+    OpusEventStructure->container->elements = malloc(app_elements_count * sizeof(CalypsoElement));
+    OpusEventStructure->container->size = app_elements_count;
+
+    OpusEventStructure->container->elements[0] =
+        make_calypso_final_element("EventDate", 14, "Event date", CALYPSO_FINAL_TYPE_DATE);
+    OpusEventStructure->container->elements[1] =
+        make_calypso_final_element("EventTime", 11, "Event time", CALYPSO_FINAL_TYPE_TIME);
+    OpusEventStructure->container->elements[2] =
+        make_calypso_final_element("EventUnknownX", 19, "Unknown X", CALYPSO_FINAL_TYPE_NUMBER);
+    OpusEventStructure->container->elements[3] = make_calypso_bitmap_element(
+        "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(
+                "EventServiceProvider", 8, "Service provider", CALYPSO_FINAL_TYPE_SERVICE_PROVIDER),
+            make_calypso_final_element("EventUnknownC", 16, "Unknown C", CALYPSO_FINAL_TYPE_NUMBER),
+            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),
+            make_calypso_final_element(
+                "EventContractPointer", 5, "Contract pointer", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_bitmap_element(
+                "EventData",
+                7,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "EventFirstStampDate", 14, "First stamp date", CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "EventFirstStampTime", 11, "First stamp time", 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),
+                    make_calypso_final_element(
+                        "EventUnknownG", 4, "Unknown G", CALYPSO_FINAL_TYPE_NUMBER),
+                    make_calypso_final_element(
+                        "EventUnknownH", 4, "Unknown H", CALYPSO_FINAL_TYPE_NUMBER),
+                    make_calypso_final_element(
+                        "EventUnknownI", 4, "Unknown I", CALYPSO_FINAL_TYPE_NUMBER),
+                }),
+        });
+
+    return OpusEventStructure;
+}
+
+CalypsoApp* get_opus_env_holder_structure() {
+    CalypsoApp* OpusEnvHolderStructure = malloc(sizeof(CalypsoApp));
+    if(!OpusEnvHolderStructure) {
+        return NULL;
+    }
+
+    int app_elements_count = 3;
+
+    OpusEnvHolderStructure->type = CALYPSO_APP_ENV_HOLDER;
+    OpusEnvHolderStructure->container = malloc(sizeof(CalypsoContainerElement));
+    OpusEnvHolderStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    OpusEnvHolderStructure->container->size = app_elements_count;
+
+    OpusEnvHolderStructure->container->elements[0] = make_calypso_final_element(
+        "EnvApplicationVersionNumber",
+        6,
+        "Numéro de version de l’application Billettique",
+        CALYPSO_FINAL_TYPE_NUMBER);
+    OpusEnvHolderStructure->container->elements[1] = make_calypso_bitmap_element(
+        "Env",
+        7,
+        (CalypsoElement[]){
+            make_calypso_final_element(
+                "EnvNetworkId", 24, "Identification du réseau", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_final_element(
+                "EnvApplicationIssuerId",
+                8,
+                "Identification de l’émetteur de l’application",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EnvApplicationValidityEndDate",
+                14,
+                "Date de fin de validité de l’application",
+                CALYPSO_FINAL_TYPE_DATE),
+            make_calypso_final_element(
+                "EnvPayMethod", 11, "Code mode de paiement", CALYPSO_FINAL_TYPE_PAY_METHOD),
+            make_calypso_final_element(
+                "EnvAuthenticator",
+                16,
+                "Code de contrôle de l’intégrité des données",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EnvSelectList",
+                32,
+                "Bitmap de tableau de paramètre multiple",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_container_element(
+                "EnvData",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "EnvDataCardStatus", 1, "Statut de la carte", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EnvData2", 0, "Données complémentaires", CALYPSO_FINAL_TYPE_UNKNOWN),
+                }),
+        });
+
+    OpusEnvHolderStructure->container->elements[2] = make_calypso_bitmap_element(
+        "Holder",
+        2,
+        (CalypsoElement[]){
+            make_calypso_container_element(
+                "HolderData",
+                5,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "HolderUnknownA", 3, "Unknown A", CALYPSO_FINAL_TYPE_NUMBER),
+                    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),
+                }),
+            make_calypso_final_element("HolderUnknownD", 8, "Unknown D", CALYPSO_FINAL_TYPE_NUMBER),
+        });
+
+    return OpusEnvHolderStructure;
+}

+ 12 - 0
api/calypso/cards/opus.h

@@ -0,0 +1,12 @@
+#include "../calypso_util.h"
+
+#ifndef OPUS_STRUCTURES_H
+#define OPUS_STRUCTURES_H
+
+CalypsoApp* get_opus_contract_structure();
+
+CalypsoApp* get_opus_event_structure();
+
+CalypsoApp* get_opus_env_holder_structure();
+
+#endif // OPUS_STRUCTURES_H

+ 513 - 0
api/calypso/transit/navigo.c

@@ -0,0 +1,513 @@
+#include "navigo.h"
+#include "navigo_lists.h"
+#include "../../../metroflip_i.h"
+
+const char* get_navigo_transport_type(int type) {
+    switch(type) {
+    case BUS_URBAIN:
+        return "Bus Urbain";
+    case BUS_INTERURBAIN:
+        return "Bus Interurbain";
+    case METRO:
+        return "Metro";
+    case TRAM:
+        return "Tram";
+    case TRAIN:
+        return "Train";
+    case PARKING:
+        return "Parking";
+    default:
+        return "Unknown";
+    }
+}
+
+const char* get_navigo_service_provider(int provider) {
+    switch(provider) {
+    case 2:
+        return "SNCF";
+    case 3:
+        return "RATP";
+    case 4:
+        return "IDF Mobilites";
+    case 10:
+        return "IDF Mobilites";
+    case 115:
+        return "CSO (VEOLIA)";
+    case 116:
+        return "R'Bus (VEOLIA)";
+    case 156:
+        return "Phebus";
+    case 175:
+        return "RATP (Veolia Transport Nanterre)";
+    default:
+        return "Unknown";
+    }
+}
+
+const char* get_transition_type(int transition) {
+    switch(transition) {
+    case 1:
+        return "Entry";
+    case 2:
+        return "Exit";
+    case 4:
+        return "Controle volant (a bord)";
+    case 5:
+        return "Test validation";
+    case 6:
+        return "Interchange - Entry";
+    case 7:
+        return "Interchange - Exit";
+    case 9:
+        return "Validation cancelled";
+    case 10:
+        return "Entry";
+    case 11:
+        return "Exit";
+    case 13:
+        return "Distribution";
+    case 15:
+        return "Invalidation";
+    default: {
+        char* transition_str = malloc(6 * sizeof(char));
+        snprintf(transition_str, 6, "%d", transition);
+        return transition_str;
+    }
+    }
+}
+
+const char* get_navigo_type(int type) {
+    switch(type) {
+    case NAVIGO_EASY:
+        return "Navigo Easy";
+    case NAVIGO_DECOUVERTE:
+        return "Navigo Decouverte";
+    case NAVIGO_STANDARD:
+        return "Navigo Standard";
+    case NAVIGO_INTEGRAL:
+        return "Navigo Integral";
+    case IMAGINE_R:
+        return "Imagine R";
+    default:
+        return "Navigo";
+    }
+}
+
+const char* get_navigo_tariff(int tariff) {
+    switch(tariff) {
+    case 0x0000:
+        return "Navigo Mois";
+    case 0x0001:
+        return "Navigo Semaine";
+    case 0x0002:
+        return "Navigo Annuel";
+    case 0x0003:
+        return "Navigo Jour";
+    case 0x0004:
+        return "Imagine R Junior";
+    case 0x0005:
+        return "Imagine R Etudiant";
+    case 0x000D:
+        return "Navigo Jeunes Week-end";
+    case 0x0015:
+        return "Paris-Visite"; // Theoric
+    case 0x1000:
+        return "Navigo Liberte+";
+    case 0x4000:
+        return "Navigo Mois 75%%";
+    case 0x4001:
+        return "Navigo Semaine 75%%";
+    case 0x4015:
+        return "Paris-Visite (Enfant)"; // Theoric
+    case 0x5000:
+        return "Tickets T+";
+    case 0x5004:
+        return "Tickets OrlyBus"; // Theoric
+    case 0x5005:
+        return "Tickets RoissyBus"; // Theoric
+    case 0x5006:
+        return "Bus-Tram"; // Theoric
+    case 0x5008:
+        return "Metro-Train-RER"; // Theoric
+    case 0x500b:
+        return "Paris <> Aeroports"; // Theoric
+    case 0x5010:
+        return "Tickets T+ (Reduit)"; // Theoric
+    case 0x5016:
+        return "Bus-Tram (Reduit)"; // Theoric
+    case 0x5018:
+        return "Metro-Train-RER (Reduit)"; // Theoric
+    case 0x501b:
+        return "Paris <> Aeroports (Reduit)"; // Theoric
+    case 0x8003:
+        return "Navigo Solidarite Gratuit";
+    default: {
+        char* tariff_str = malloc(6 * sizeof(char));
+        snprintf(tariff_str, 6, "%d", tariff);
+        return tariff_str;
+    }
+    }
+}
+
+bool is_ticket_count_available(int tariff) {
+    return tariff >= 0x5000 && tariff <= 0x501b;
+}
+
+const char* get_pay_method(int pay_method) {
+    switch(pay_method) {
+    case 0x30:
+        return "Apple Pay";
+    case 0x80:
+        return "Debit PME";
+    case 0x90:
+        return "Cash";
+    case 0xA0:
+        return "Mobility Check";
+    case 0xB3:
+        return "Payment Card";
+    case 0xA4:
+        return "Check";
+    case 0xA5:
+        return "Vacation Check";
+    case 0xB7:
+        return "Telepayment";
+    case 0xD0:
+        return "Remote Payment";
+    case 0xD7:
+        return "Voucher, Prepayment, Exchange Voucher, Travel Voucher";
+    case 0xD9:
+        return "Discount Voucher";
+    default:
+        return "Unknown";
+    }
+}
+
+const char* get_zones(int* zones) {
+    if(zones[0] && zones[4]) {
+        return "All Zones (1-5)";
+    } else if(zones[0] && zones[3]) {
+        return "Zones 1-4";
+    } else if(zones[0] && zones[2]) {
+        return "Zones 1-3";
+    } else if(zones[0] && zones[1]) {
+        return "Zones 1-2";
+    } else if(zones[0]) {
+        return "Zone 1";
+    } else if(zones[1] && zones[4]) {
+        return "Zones 2-5";
+    } else if(zones[1] && zones[3]) {
+        return "Zones 2-4";
+    } else if(zones[1] && zones[2]) {
+        return "Zones 2-3";
+    } else if(zones[1]) {
+        return "Zone 2";
+    } else if(zones[2] && zones[4]) {
+        return "Zones 3-5";
+    } else if(zones[2] && zones[3]) {
+        return "Zones 3-4";
+    } else if(zones[2]) {
+        return "Zone 3";
+    } else if(zones[3] && zones[4]) {
+        return "Zones 4-5";
+    } else if(zones[3]) {
+        return "Zone 4";
+    } else if(zones[4]) {
+        return "Zone 5";
+    } else {
+        return "Unknown";
+    }
+}
+
+const char* get_intercode_version(int version) {
+    // version is a 6 bits int
+    // if the first 3 bits are 000, it's a 1.x version
+    // if the first 3 bits are 001, it's a 2.x version
+    // else, it's unknown
+    int major = (version >> 3) & 0x07;
+    if(major == 0) {
+        return "Intercode I";
+    } else if(major == 1) {
+        return "Intercode II";
+    }
+    return "Unknown";
+}
+
+int get_intercode_subversion(int version) {
+    // subversion is a 3 bits int
+    return version & 0x07;
+}
+
+const char* get_navigo_metro_station(int station_group_id, int station_id) {
+    // Use NAVIGO_H constants
+    if(station_group_id < 32 && station_id < 16) {
+        return NAVIGO_METRO_STATION_LIST[station_group_id][station_id];
+    }
+    // cast station_group_id-station_id to a string
+    char* station = malloc(12 * sizeof(char));
+    if(!station) {
+        return "Unknown";
+    }
+    snprintf(station, 10, "%d-%d", station_group_id, station_id);
+    return station;
+}
+
+const char* get_navigo_train_line(int station_group_id) {
+    if(station_group_id < 77) {
+        return NAVIGO_TRAIN_LINES_LIST[station_group_id];
+    }
+    return "Unknown";
+}
+
+const char* get_navigo_train_station(int station_group_id, int station_id) {
+    if(station_group_id < 77 && station_id < 19) {
+        return NAVIGO_TRAIN_STATION_LIST[station_group_id][station_id];
+    }
+    // cast station_group_id-station_id to a string
+    char* station = malloc(12 * sizeof(char));
+    if(!station) {
+        return "Unknown";
+    }
+    snprintf(station, 10, "%d-%d", station_group_id, station_id);
+    return station;
+}
+
+const char* get_navigo_tram_line(int route_number) {
+    switch(route_number) {
+    case 16:
+        return "T6";
+    default: {
+        char* line = malloc(3 * sizeof(char));
+        if(!line) {
+            return "Unknown";
+        }
+        snprintf(line, 3, "T%d", route_number);
+        return line;
+    }
+    }
+}
+
+void show_navigo_event_info(
+    NavigoCardEvent* event,
+    NavigoCardContract* contracts,
+    FuriString* parsed_data) {
+    if(event->used_contract == 0) {
+        furi_string_cat_printf(parsed_data, "No event data\n");
+        return;
+    }
+    if(event->transport_type == BUS_URBAIN || event->transport_type == BUS_INTERURBAIN ||
+       event->transport_type == METRO || event->transport_type == TRAM) {
+        if(event->route_number_available) {
+            if(event->transport_type == METRO && event->route_number == 103) {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s 3 bis\n%s\n",
+                    get_navigo_transport_type(event->transport_type),
+                    get_transition_type(event->transition));
+            } else if(event->transport_type == TRAM) {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s %s\n%s\n",
+                    get_navigo_transport_type(event->transport_type),
+                    get_navigo_tram_line(event->route_number),
+                    get_transition_type(event->transition));
+            } else {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s %d\n%s\n",
+                    get_navigo_transport_type(event->transport_type),
+                    event->route_number,
+                    get_transition_type(event->transition));
+            }
+        } else {
+            furi_string_cat_printf(
+                parsed_data,
+                "%s\n%s\n",
+                get_navigo_transport_type(event->transport_type),
+                get_transition_type(event->transition));
+        }
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        if(event->transport_type == METRO) {
+            furi_string_cat_printf(
+                parsed_data,
+                "Station: %s\nSector: %s\n",
+                get_navigo_metro_station(event->station_group_id, event->station_id),
+                get_navigo_metro_station(event->station_group_id, 0));
+        } else {
+            furi_string_cat_printf(
+                parsed_data, "Station ID: %d-%d\n", event->station_group_id, event->station_id);
+        }
+        if(event->location_gate_available) {
+            furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
+        }
+        if(event->device_available) {
+            if(event->transport_type == BUS_URBAIN || event->transport_type == BUS_INTERURBAIN) {
+                const char* side = event->side == 0 ? "right" : "left";
+                furi_string_cat_printf(parsed_data, "Door: %d\nSide: %s\n", event->door, side);
+            } else {
+                furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+            }
+        }
+        if(event->mission_available) {
+            furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
+        }
+        if(event->vehicle_id_available) {
+            furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
+        }
+        if(event->used_contract_available) {
+            furi_string_cat_printf(
+                parsed_data,
+                "Contract: %d - %s\n",
+                event->used_contract,
+                get_navigo_tariff(contracts[event->used_contract - 1].tariff));
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    } else if(event->transport_type == TRAIN) {
+        if(event->route_number_available) {
+            furi_string_cat_printf(
+                parsed_data,
+                "RER %c\n%s\n",
+                (65 + event->route_number - 17),
+                get_transition_type(event->transition));
+        } else {
+            furi_string_cat_printf(
+                parsed_data,
+                "%s %s\n%s\n",
+                get_navigo_transport_type(event->transport_type),
+                get_navigo_train_line(event->station_group_id),
+                get_transition_type(event->transition));
+        }
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        furi_string_cat_printf(
+            parsed_data,
+            "Station: %s\n",
+            get_navigo_train_station(event->station_group_id, event->station_id));
+        /* if(event->route_number_available) {
+            furi_string_cat_printf(parsed_data, "Route: %d\n", event->route_number);
+        } */
+        if(event->location_gate_available) {
+            furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
+        }
+        if(event->device_available) {
+            if(event->service_provider == 2) {
+                furi_string_cat_printf(parsed_data, "Device: %d\n", event->device & 0xFF);
+            } else {
+                furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+            }
+        }
+        if(event->mission_available) {
+            furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
+        }
+        if(event->vehicle_id_available) {
+            furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
+        }
+        if(event->used_contract_available) {
+            furi_string_cat_printf(
+                parsed_data,
+                "Contract: %d - %s\n",
+                event->used_contract,
+                get_navigo_tariff(contracts[event->used_contract - 1].tariff));
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    } else {
+        furi_string_cat_printf(
+            parsed_data,
+            "%s - %s\n",
+            get_navigo_transport_type(event->transport_type),
+            get_transition_type(event->transition));
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        furi_string_cat_printf(
+            parsed_data, "Station ID: %d-%d\n", event->station_group_id, event->station_id);
+        if(event->location_gate_available) {
+            furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
+        }
+        if(event->device_available) {
+            furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+        }
+        if(event->mission_available) {
+            furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
+        }
+        if(event->vehicle_id_available) {
+            furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
+        }
+        if(event->used_contract_available) {
+            furi_string_cat_printf(
+                parsed_data,
+                "Contract: %d - %s\n",
+                event->used_contract,
+                get_navigo_tariff(contracts[event->used_contract - 1].tariff));
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    }
+}
+
+void show_navigo_contract_info(NavigoCardContract* contract, FuriString* parsed_data) {
+    furi_string_cat_printf(parsed_data, "Type: %s\n", get_navigo_tariff(contract->tariff));
+    if(is_ticket_count_available(contract->tariff)) {
+        furi_string_cat_printf(parsed_data, "Remaining Tickets: %d\n", contract->counter.count);
+    }
+    if(contract->serial_number_available) {
+        furi_string_cat_printf(parsed_data, "TCN Number: %d\n", contract->serial_number);
+    }
+    if(contract->pay_method_available) {
+        furi_string_cat_printf(
+            parsed_data, "Payment Method: %s\n", get_pay_method(contract->pay_method));
+    }
+    if(contract->price_amount_available) {
+        furi_string_cat_printf(parsed_data, "Amount: %.2f EUR\n", contract->price_amount);
+    }
+    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));
+    }
+    furi_string_cat_printf(parsed_data, "Sold on: ");
+    locale_format_datetime_cat(parsed_data, &contract->sale_date, false);
+    furi_string_cat_printf(parsed_data, "\n");
+    furi_string_cat_printf(
+        parsed_data, "Sales Agent: %s\n", get_navigo_service_provider(contract->sale_agent));
+    furi_string_cat_printf(parsed_data, "Sales Terminal: %d\n", contract->sale_device);
+    if(contract->status == 1) {
+        furi_string_cat_printf(parsed_data, "Status: OK\n");
+    } else {
+        furi_string_cat_printf(parsed_data, "Status: Unknown (%d)\n", contract->status);
+    }
+    furi_string_cat_printf(parsed_data, "Authenticity Code: %d\n", contract->authenticator);
+}
+
+void show_navigo_environment_info(NavigoCardEnv* environment, FuriString* parsed_data) {
+    furi_string_cat_printf(
+        parsed_data,
+        "App Version: %s - v%d\n",
+        get_intercode_version(environment->app_version),
+        get_intercode_subversion(environment->app_version));
+    furi_string_cat_printf(
+        parsed_data, "Country: %s\n", get_country_string(environment->country_num));
+    furi_string_cat_printf(
+        parsed_data,
+        "Network: %s\n",
+        get_network_string(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, "\n");
+}

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

@@ -0,0 +1,47 @@
+#include "../calypso_util.h"
+#include "../cards/intercode.h"
+#include "navigo_i.h"
+#include <datetime.h>
+#include <stdbool.h>
+#include <furi.h>
+
+#ifndef NAVIGO_H
+#define NAVIGO_H
+
+const char* get_navigo_type(int type);
+
+const char* get_navigo_metro_station(int station_group_id, int station_id);
+
+const char* get_navigo_train_line(int station_group_id);
+
+const char* get_navigo_train_station(int station_group_id, int station_id);
+
+const char* get_navigo_tram_line(int route_number);
+
+void show_navigo_event_info(
+    NavigoCardEvent* event,
+    NavigoCardContract* contracts,
+    FuriString* parsed_data);
+
+void show_navigo_contract_info(NavigoCardContract* contract, FuriString* parsed_data);
+
+void show_navigo_environment_info(NavigoCardEnv* environment, FuriString* parsed_data);
+
+typedef enum {
+    BUS_URBAIN = 1,
+    BUS_INTERURBAIN = 2,
+    METRO = 3,
+    TRAM = 4,
+    TRAIN = 5,
+    PARKING = 8
+} NAVIGO_TRANSPORT_TYPE;
+
+typedef enum {
+    NAVIGO_EASY = 0,
+    NAVIGO_DECOUVERTE = 1,
+    NAVIGO_STANDARD = 2,
+    NAVIGO_INTEGRAL = 6,
+    IMAGINE_R = 14
+} NAVIGO_CARD_STATUS;
+
+#endif // NAVIGO_H

+ 4 - 8
scenes/navigo_structs.h → api/calypso/transit/navigo_i.h

@@ -1,6 +1,8 @@
 #include <datetime.h>
 #include <stdbool.h>
-#include <furi.h>
+
+#ifndef NAVIGO_I_H
+#define NAVIGO_I_H
 
 typedef struct {
     int transport_type;
@@ -70,12 +72,6 @@ typedef struct {
     NavigoCardHolder holder;
     NavigoCardContract contracts[4];
     NavigoCardEvent events[3];
-    unsigned int card_number;
 } NavigoCardData;
 
-typedef struct {
-    NavigoCardData* card;
-    int page_id;
-    // mutex
-    FuriMutex* mutex;
-} NavigoContext;
+#endif // NAVIGO_I_H

+ 6 - 69
scenes/navigo.h → api/calypso/transit/navigo_lists.h

@@ -1,70 +1,7 @@
-#include <gui/gui.h>
-#include <gui/modules/widget_elements/widget_element.h>
-#include "../api/calypso/calypso_util.h"
-#include "../api/calypso/cards/intercode.h"
-#include <datetime.h>
-#include <stdbool.h>
+#ifndef NAVIGO_LISTS_H
+#define NAVIGO_LISTS_H
 
-#ifndef METRO_LIST_H
-#define METRO_LIST_H
-
-#ifndef NAVIGO_H
-#define NAVIGO_H
-
-void metroflip_back_button_widget_callback(GuiButtonType result, InputType type, void* context);
-void metroflip_next_button_widget_callback(GuiButtonType result, InputType type, void* context);
-
-// Service Providers
-static const char* SERVICE_PROVIDERS[] = {
-    [2] = "SNCF",
-    [3] = "RATP",
-    [115] = "CSO (VEOLIA)",
-    [116] = "R'Bus (VEOLIA)",
-    [156] = "Phebus",
-    [175] = "RATP (Veolia Transport Nanterre)"};
-
-// Transport Types
-static const char* TRANSPORT_LIST[] = {
-    [1] = "Bus Urbain",
-    [2] = "Bus Interurbain",
-    [3] = "Metro",
-    [4] = "Tram",
-    [5] = "Train",
-    [8] = "Parking"};
-
-typedef enum {
-    BUS_URBAIN = 1,
-    BUS_INTERURBAIN = 2,
-    METRO = 3,
-    TRAM = 4,
-    TRAIN = 5,
-    PARKING = 8
-} TRANSPORT_TYPE;
-
-typedef enum {
-    NAVIGO_EASY = 0,
-    NAVIGO_DECOUVERTE = 1,
-    NAVIGO_STANDARD = 2,
-    NAVIGO_INTEGRAL = 6,
-    IMAGINE_R = 14
-} CARD_STATUS;
-
-// Transition Types
-static const char* TRANSITION_LIST[] = {
-    [1] = "Validation en entree",
-    [2] = "Validation en sortie",
-    [4] = "Controle volant (a bord)",
-    [5] = "Validation de test",
-    [6] = "Validation en correspondance (entree)",
-    [7] = "Validation en correspondance (sortie)",
-    [9] = "Annulation de validation",
-    [10] = "Validation en entree",
-    [13] = "Distribution",
-    [15] = "Invalidation"};
-
-#endif // NAVIGO_H
-
-static const char* METRO_STATION_LIST[32][16] =
+static const char* NAVIGO_METRO_STATION_LIST[32][16] =
     {[1] =
          {[0] = "Cite",
           [1] = "Saint-Michel",
@@ -435,7 +372,7 @@ static const char* METRO_STATION_LIST[32][16] =
          [13] = "Place de Clichy",
          [14] = "La Fourche"}};
 
-static const char* TRAIN_LINES_LIST[77] = {
+static const char* NAVIGO_TRAIN_LINES_LIST[77] = {
     [1] = "RER B",         [3] = "RER B",         [6] = "RER A",         [14] = "RER B",
     [15] = "RER B",        [16] = "RER A",        [17] = "RER A",        [18] = "RER B",
     [20] = "Transilien P", [21] = "Transilien P", [22] = "T4",           [23] = "Transilien P",
@@ -448,7 +385,7 @@ static const char* 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* TRAIN_STATION_LIST[77][19] = {
+static const char* NAVIGO_TRAIN_STATION_LIST[77][19] = {
     [1] = {[0] = "Châtelet-Les Halles", [1] = "Châtelet-Les Halles", [7] = "Luxembourg"},
     [3] = {[0] = "Saint-Michel Notre-Dame"},
     [6] = {[0] = "Auber", [6] = "Auber"},
@@ -729,4 +666,4 @@ static const char* TRAIN_STATION_LIST[77][19] = {
          [15] = "Dourdan | Dourdan-la-Foret"},
 };
 
-#endif // METRO_LIST_H
+#endif

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

@@ -0,0 +1,125 @@
+#include "opus.h"
+#include "opus_lists.h"
+#include "../../../metroflip_i.h"
+
+const char* get_opus_service_provider(int provider) {
+    switch(provider) {
+    case 0x01:
+        return "STM";
+    case 0x02:
+        return "STM";
+    case 0x03:
+        return "RTL";
+    case 0x04:
+        return "RTM";
+    case 0x05:
+        return "RTC";
+    case 0x06:
+        return "STL";
+    case 0x10:
+        return "STLevis";
+    default: {
+        char* provider_str = malloc(10 * sizeof(char));
+        if(!provider_str) {
+            return "Unknown";
+        }
+        snprintf(provider_str, 10, "0x%02X", provider);
+        return provider_str;
+    }
+    }
+}
+
+const char* get_opus_transport_type(int route_number) {
+    if(route_number >= 0x01 && route_number <= 0x04) {
+        return "Metro";
+    } else {
+        return "Bus";
+    }
+}
+
+const char* get_opus_transport_line(int route_number) {
+    if(OPUS_LINES_LIST[route_number]) {
+        return OPUS_LINES_LIST[route_number];
+    } else {
+        char* line = malloc(4 * sizeof(char));
+        if(!line) {
+            return "Unknown";
+        }
+        snprintf(line, 4, "%d", route_number);
+        return line;
+    }
+}
+
+const char* get_opus_tariff(int tariff) {
+    switch(tariff) {
+    case 0xb1:
+        return "Monthly";
+    case 0xb2:
+    case 0xc9:
+        return "Weekly";
+    case 0x1c7:
+        return "Single Trips";
+    case 0xa34:
+        return "Monthly Student";
+    case 0xa3e:
+        return "Weekly";
+    default: {
+        char* tariff_str = malloc(9 * sizeof(char));
+        if(!tariff_str) {
+            return "Unknown";
+        }
+        snprintf(tariff_str, 9, "0x%02X", tariff);
+        return tariff_str;
+    }
+    }
+}
+
+void show_opus_event_info(
+    OpusCardEvent* event,
+    OpusCardContract* contracts,
+    FuriString* parsed_data) {
+    UNUSED(contracts);
+    furi_string_cat_printf(
+        parsed_data,
+        "%s %s\n",
+        get_opus_transport_type(event->route_number),
+        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));
+    }
+    locale_format_datetime_cat(parsed_data, &event->date, true);
+    furi_string_cat_printf(parsed_data, "\n");
+}
+
+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: ");
+    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);
+}
+
+void show_opus_environment_info(OpusCardEnv* environment, 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));
+    furi_string_cat_printf(
+        parsed_data,
+        "Network: %s\n",
+        get_network_string(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, "\n");
+}

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

@@ -0,0 +1,17 @@
+#include "../calypso_util.h"
+#include "../cards/opus.h"
+#include "opus_i.h"
+
+#ifndef OPUS_H
+#define OPUS_H
+
+void show_opus_event_info(
+    OpusCardEvent* event,
+    OpusCardContract* contracts,
+    FuriString* parsed_data);
+
+void show_opus_contract_info(OpusCardContract* contract, FuriString* parsed_data);
+
+void show_opus_environment_info(OpusCardEnv* environment, FuriString* parsed_data);
+
+#endif // OPUS_H

+ 45 - 0
api/calypso/transit/opus_i.h

@@ -0,0 +1,45 @@
+#include <datetime.h>
+#include <stdbool.h>
+
+#ifndef OPUS_I_H
+#define OPUS_I_H
+
+typedef struct {
+    int service_provider;
+    int route_number;
+    bool route_number_available;
+    int used_contract;
+    bool used_contract_available;
+    DateTime date;
+} OpusCardEvent;
+
+typedef struct {
+    int app_version;
+    int country_num;
+    int network_num;
+    DateTime end_dt;
+} OpusCardEnv;
+
+typedef struct {
+    int card_status;
+    int commercial_id;
+} OpusCardHolder;
+
+typedef struct {
+    int provider;
+    int tariff;
+    DateTime start_date;
+    DateTime end_date;
+    DateTime sale_date;
+    int status;
+    bool present;
+} OpusCardContract;
+
+typedef struct {
+    OpusCardEnv environment;
+    OpusCardHolder holder;
+    OpusCardContract contracts[4];
+    OpusCardEvent events[3];
+} OpusCardData;
+
+#endif // OPUS_I_H

+ 241 - 0
api/calypso/transit/opus_lists.h

@@ -0,0 +1,241 @@
+/*
+<bus id="2" logo="stm_orange" name="METRO Ligne orange" />
+    <bus id="1" logo="stm_vert" name="METRO Ligne verte" />
+    <bus id="3" logo="stm_jaune" name="METRO Ligne jaune" />
+    <bus id="4" logo="stm_bleue" name="METRO Ligne bleue" />
+    <bus id="" logo="stm_local" name="10" />
+    <bus id="" logo="stm_local" name="11" />
+    <bus id="" logo="stm_local" name="12" />
+    <bus id="" logo="stm_local" name="13" />
+    <bus id="" logo="stm_local" name="14" />
+    <bus id="" logo="stm_local" name="15" />
+    <bus id="" logo="stm_local" name="16" />
+    <bus id="" logo="stm_local" name="17" />
+    <bus id="13" logo="stm_local" name="18" />
+    <bus id="" logo="stm_local" name="19" />
+    <bus id="" logo="stm_local" name="21" />
+    <bus id="" logo="stm_local" name="22" />
+    <bus id="15" logo="stm_local" name="24" />
+    <bus id="16" logo="stm_local" name="25" />
+    <bus id="231" logo="stm_local" name="26" />
+    <bus id="17" logo="stm_local" name="27" />
+    <bus id="18" logo="stm_local" name="28" />
+    <bus id="" logo="stm_local" name="29" />
+    <bus id="" logo="stm_local" name="30" />
+    <bus id="" logo="stm_local" name="31" />
+    <bus id="22" logo="stm_local" name="32" />
+    <bus id="23" logo="stm_local" name="33" />
+    <bus id="24" logo="stm_local" name="34" />
+    <bus id="" logo="stm_local" name="36" />
+    <bus id="26" logo="stm_local" name="37" />
+    <bus id="" logo="stm_local" name="39" />
+    <bus id="" logo="stm_local" name="40" />
+    <bus id="226" logo="stm_local" name="41" />
+    <bus id="29" logo="stm_local" name="43" />
+    <bus id="30" logo="stm_local" name="44" />
+    <bus id="31" logo="stm_local" name="45" />
+    <bus id="" logo="stm_local" name="46" />
+    <bus id="33" logo="stm_local" name="47" />
+    <bus id="34" logo="stm_local" name="48" />
+    <bus id="35" logo="stm_local" name="49" />
+    <bus id="36" logo="stm_local" name="51" />
+    <bus id="" logo="stm_local" name="52" />
+    <bus id="" logo="stm_local" name="53" />
+    <bus id="" logo="stm_local" name="54" />
+    <bus id="40" logo="stm_local" name="55" />
+    <bus id="" logo="stm_local" name="56" />
+    <bus id="" logo="stm_local" name="57" />
+    <bus id="" logo="stm_local" name="58" />
+    <bus id="44" logo="stm_local" name="61" />
+    <bus id="" logo="stm_local" name="63" />
+    <bus id="46" logo="stm_local" name="64" />
+    <bus id="" logo="stm_local" name="66" />
+    <bus id="48" logo="stm_local" name="67" />
+    <bus id="49" logo="stm_local" name="68" />
+    <bus id="50" logo="stm_local" name="69" />
+    <bus id="51" logo="stm_local" name="70" />
+    <bus id="" logo="stm_local" name="71" />
+    <bus id="" logo="stm_local" name="72" />
+    <bus id="" logo="stm_local" name="73" />
+    <bus id="" logo="stm_local" name="74" />
+    <bus id="" logo="stm_local" name="75" />
+    <bus id="" logo="stm_local" name="76" />
+    <bus id="" logo="stm_local" name="77" />
+    <bus id="" logo="stm_local" name="78" />
+    <bus id="59" logo="stm_local" name="80" />
+    <bus id="60" logo="stm_local" name="85" />
+    <bus id="61" logo="stm_local" name="86" />
+    <bus id="63" logo="stm_local" name="90" />
+    <bus id="" logo="stm_local" name="92" />
+    <bus id="65" logo="stm_local" name="93" />
+    <bus id="" logo="stm_local" name="94" />
+    <bus id="" logo="stm_local" name="95" />
+    <bus id="68" logo="stm_local" name="97" />
+    <bus id="" logo="stm_local" name="99" />
+    <bus id="" logo="stm_local" name="100" />
+    <bus id="" logo="stm_local" name="101" />
+    <bus id="" logo="stm_local" name="102" />
+    <bus id="73" logo="stm_local" name="103" />
+    <bus id="74" logo="stm_local" name="104" />
+    <bus id="75" logo="stm_local" name="105" />
+    <bus id="" logo="stm_local" name="106" />
+    <bus id="77" logo="stm_local" name="107" />
+    <bus id="" logo="stm_local" name="108" />
+    <bus id="79" logo="stm_local" name="109" />
+    <bus id="80" logo="stm_local" name="110" />
+    <bus id="81" logo="stm_local" name="112" />
+    <bus id="82" logo="stm_local" name="113" />
+    <bus id="" logo="stm_local" name="115" />
+    <bus id="" logo="stm_local" name="116" />
+    <bus id="" logo="stm_local" name="117" />
+    <bus id="" logo="stm_local" name="119" />
+    <bus id="87" logo="stm_local" name="121" />
+    <bus id="" logo="stm_local" name="123" />
+    <bus id="89" logo="stm_local" name="124" />
+    <bus id="90" logo="stm_local" name="125" />
+    <bus id="" logo="stm_local" name="126" />
+    <bus id="92" logo="stm_local" name="128" />
+    <bus id="93" logo="stm_local" name="129" />
+    <bus id="94" logo="stm_local" name="131" />
+    <bus id="" logo="stm_local" name="135" />
+    <bus id="234" logo="stm_local" name="136" />
+    <bus id="97" logo="stm_local" name="138" />
+    <bus id="98" logo="stm_local" name="139" />
+    <bus id="99" logo="stm_local" name="140" />
+    <bus id="100" logo="stm_local" name="141" />
+    <bus id="" logo="stm_local" name="144" />
+    <bus id="103" logo="stm_local" name="146" />
+    <bus id="" logo="stm_local" name="150" />
+    <bus id="" logo="stm_local" name="160" />
+    <bus id="108" logo="stm_local" name="161" />
+    <bus id="109" logo="stm_local" name="162" />
+    <bus id="110" logo="stm_local" name="164" />
+    <bus id="111" logo="stm_local" name="165" />
+    <bus id="" logo="stm_local" name="166" />
+    <bus id="113" logo="stm_local" name="168" />
+    <bus id="" logo="stm_local" name="170" />
+    <bus id="116" logo="stm_local" name="171" />
+    <bus id="" logo="stm_local" name="174" />
+    <bus id="" logo="stm_local" name="175" />
+    <bus id="120" logo="stm_local" name="177" />
+    <bus id="" logo="stm_local" name="178" />
+    <bus id="121" logo="stm_local" name="179" />
+    <bus id="" logo="stm_local" name="180" />
+    <bus id="" logo="stm_local" name="183" />
+    <bus id="126" logo="stm_local" name="185" />
+    <bus id="" logo="stm_local" name="186" />
+    <bus id="128" logo="stm_local" name="187" />
+    <bus id="" logo="stm_local" name="188" />
+    <bus id="130" logo="stm_local" name="189" />
+    <bus id="132" logo="stm_local" name="191" />
+    <bus id="134" logo="stm_local" name="192" />
+    <bus id="134" logo="stm_local" name="193" />
+    <bus id="136" logo="stm_local" name="195" />
+    <bus id="" logo="stm_local" name="196" />
+    <bus id="138" logo="stm_local" name="197" />
+    <bus id="" logo="stm_local" name="200" />
+    <bus id="141" logo="stm_local" name="201" />
+    <bus id="" logo="stm_local" name="202" />
+    <bus id="" logo="stm_local" name="203" />
+    <bus id="" logo="stm_local" name="204" />
+    <bus id="145" logo="stm_local" name="205" />
+    <bus id="" logo="stm_local" name="206" />
+    <bus id="147" logo="stm_local" name="207" />
+    <bus id="148" logo="stm_local" name="208" />
+    <bus id="149" logo="stm_local" name="209" />
+    <bus id="151" logo="stm_local" name="211" />
+    <bus id="" logo="stm_local" name="212" />
+    <bus id="" logo="stm_local" name="213" />
+    <bus id="" logo="stm_local" name="215" />
+    <bus id="155" logo="stm_local" name="216" />
+    <bus id="156" logo="stm_local" name="217" />
+    <bus id="" logo="stm_local" name="218" />
+    <bus id="" logo="stm_local" name="219" />
+    <bus id="" logo="stm_local" name="220" />
+    <bus id="" logo="stm_local" name="225" />
+    <bus id="" logo="stm_night" name="350" />
+    <bus id="" logo="stm_night" name="353" />
+    <bus id="" logo="stm_night" name="354" />
+    <bus id="" logo="stm_night" name="355" />
+    <bus id="" logo="stm_night" name="356" />
+    <bus id="" logo="stm_night" name="357" />
+    <bus id="" logo="stm_night" name="358" />
+    <bus id="" logo="stm_night" name="359" />
+    <bus id="" logo="stm_night" name="360" />
+    <bus id="" logo="stm_night" name="361" />
+    <bus id="" logo="stm_night" name="362" />
+    <bus id="" logo="stm_night" name="363" />
+    <bus id="" logo="stm_night" name="364" />
+    <bus id="" logo="stm_night" name="365" />
+    <bus id="" logo="stm_night" name="368" />
+    <bus id="" logo="stm_night" name="369" />
+    <bus id="" logo="stm_night" name="370" />
+    <bus id="" logo="stm_night" name="371" />
+    <bus id="" logo="stm_night" name="372" />
+    <bus id="" logo="stm_night" name="376" />
+    <bus id="" logo="stm_night" name="378" />
+    <bus id="" logo="stm_night" name="380" />
+    <bus id="" logo="stm_night" name="382" />
+    <bus id="237" logo="stm_express" name="401" />
+    <bus id="261" logo="stm_express" name="405" />
+    <bus id="" logo="stm_express" name="406" />
+    <bus id="" logo="stm_express" name="407" />
+    <bus id="" logo="stm_express" name="409" />
+    <bus id="165" logo="stm_express" name="410" />
+    <bus id="" logo="stm_express" name="411" />
+    <bus id="242" logo="stm_express" name="419" />
+    <bus id="" logo="stm_express" name="420" />
+    <bus id="" logo="stm_express" name="425" />
+    <bus id="218" logo="stm_express" name="427" />
+    <bus id="" logo="stm_express" name="428" />
+    <bus id="" logo="stm_express" name="430" />
+    <bus id="" logo="stm_express" name="432" />
+    <bus id="" logo="stm_express" name="435" />
+    <bus id="" logo="stm_express" name="439" />
+    <bus id="" logo="stm_express" name="440" />
+    <bus id="" logo="stm_express" name="444" />
+    <bus id="" logo="stm_express" name="445" />
+    <bus id="" logo="stm_express" name="448" />
+    <bus id="" logo="stm_express" name="449" />
+    <bus id="" logo="stm_express" name="460" />
+    <bus id="" logo="stm_express" name="465" />
+    <bus id="211" logo="stm_express" name="467" />
+    <bus id="" logo="stm_express" name="468" />
+    <bus id="" logo="stm_express" name="469" />
+    <bus id="169" logo="stm_express" name="470" />
+    <bus id="" logo="stm_express" name="475" />
+    <bus id="" logo="stm_express" name="480" />
+    <bus id="" logo="stm_express" name="485" />
+    <bus id="" logo="stm_express" name="486" />
+    <bus id="253" logo="stm_express" name="487" />
+    <bus id="" logo="stm_express" name="491" />
+    <bus id="255" logo="stm_express" name="495" />
+    <bus id="256" logo="stm_express" name="496" />
+    <bus id="257" logo="stm_shuttle" name="715" />
+    <bus id="219" logo="stm_shuttle" name="747" />
+    <bus id="" logo="stm_shuttle" name="777" />
+    <bus id="" logo="stm_local" name="252" />
+    <bus id="" logo="stm_local" name="253" />
+    <bus id="" logo="stm_local" name="254" />
+    <bus id="" logo="stm_local" name="256" />
+    <bus id="" logo="stm_local" name="257" />
+    <bus id="" logo="stm_local" name="258" />
+    <bus id="" logo="stm_local" name="259" />
+    <bus id="" logo="stm_local" name="260" />
+    <bus id="" logo="stm_local" name="262" />
+    <bus id="" logo="stm_local" name="263" />
+*/
+
+#ifndef OPUS_LISTS_H
+#define OPUS_LISTS_H
+
+static const char* OPUS_LINES_LIST[512] = {
+    [1] = "Green",
+    [2] = "Orange",
+    [3] = "Yellow",
+    [4] = "Blue",
+
+    [219] = "747",
+};
+
+#endif // OPUS_LISTS_H

+ 3 - 3
metroflip_i.h

@@ -44,7 +44,7 @@ extern const Icon I_RFIDDolphinReceive_97x61;
 
 #include "scenes/metroflip_scene.h"
 
-#include "scenes/navigo_structs.h"
+#include "api/calypso/calypso_i.h"
 
 typedef struct {
     Gui* gui;
@@ -75,8 +75,8 @@ typedef struct {
     char currency[4];
     char card_type[32];
 
-    // Navigo specific context
-    NavigoContext* navigo_context;
+    // Calypso specific context
+    CalypsoContext* calypso_context;
 } Metroflip;
 
 enum MetroflipCustomEvent {

Разлика између датотеке није приказан због своје велике величине
+ 381 - 923
scenes/metroflip_scene_calypso.c


+ 10 - 0
scenes/metroflip_scene_calypso.h

@@ -0,0 +1,10 @@
+#include <gui/gui.h>
+#include <gui/modules/widget_elements/widget_element.h>
+#include "../api/calypso/transit/navigo.h"
+#include "../api/calypso/transit/opus.h"
+#include "../api/calypso/calypso_i.h"
+#include <datetime.h>
+#include <stdbool.h>
+
+void metroflip_back_button_widget_callback(GuiButtonType result, InputType type, void* context);
+void metroflip_next_button_widget_callback(GuiButtonType result, InputType type, void* context);

Неке датотеке нису приказане због велике количине промена