diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.c b/applications/main/bad_usb/helpers/bad_usb_hid.c index 497394769..dcba7b5e9 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.c +++ b/applications/main/bad_usb/helpers/bad_usb_hid.c @@ -69,7 +69,6 @@ static const BadUsbHidApi hid_api_usb = { .release_all = hid_usb_release_all, .get_led_state = hid_usb_get_led_state, }; -const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface) { - UNUSED(interface); +const BadUsbHidApi* bad_usb_hid_get_interface() { return &hid_api_usb; } diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.h b/applications/main/bad_usb/helpers/bad_usb_hid.h index 770230c63..feaaacd54 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.h +++ b/applications/main/bad_usb/helpers/bad_usb_hid.h @@ -7,10 +7,6 @@ extern "C" { #include #include -typedef enum { - BadUsbHidInterfaceUsb, -} BadUsbHidInterface; - typedef struct { void* (*init)(FuriHalUsbHidConfig* hid_cfg); void (*deinit)(void* inst); @@ -25,7 +21,7 @@ typedef struct { uint8_t (*get_led_state)(void* inst); } BadUsbHidApi; -const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface); +const BadUsbHidApi* bad_usb_hid_get_interface(); void bad_usb_hid_ble_remove_pairing(void); diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 8408b96ae..ea2339006 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -654,7 +654,7 @@ static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) { memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout))); } -BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) { +BadUsbScript* bad_usb_script_open(FuriString* file_path) { furi_assert(file_path); BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); @@ -664,7 +664,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface inte bad_usb->st.state = BadUsbStateInit; bad_usb->st.error[0] = '\0'; - bad_usb->hid = bad_usb_hid_get_interface(interface); + bad_usb->hid = bad_usb_hid_get_interface(); bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb); furi_thread_start(bad_usb->thread); diff --git a/applications/main/bad_usb/helpers/ducky_script.h b/applications/main/bad_usb/helpers/ducky_script.h index 9519623f6..43969d7b6 100644 --- a/applications/main/bad_usb/helpers/ducky_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -34,7 +34,7 @@ typedef struct { typedef struct BadUsbScript BadUsbScript; -BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface); +BadUsbScript* bad_usb_script_open(FuriString* file_path); void bad_usb_script_close(BadUsbScript* bad_usb); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 20e6d75e3..0afc056b6 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -39,7 +39,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { void bad_usb_scene_work_on_enter(void* context) { BadUsbApp* app = context; - app->bad_usb_script = bad_usb_script_open(app->file_path, BadUsbHidInterfaceUsb); + app->bad_usb_script = bad_usb_script_open(app->file_path); bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); FuriString* file_name; diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c index 3138d790b..261f24ce0 100644 --- a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c @@ -2,6 +2,20 @@ #define TAG "Mosgortrans" +void render_section_header( + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + for(uint8_t i = 0; i < prefix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } + furi_string_cat_printf(str, "[ %s ]", name); + for(uint8_t i = 0; i < suffix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } +} + void from_days_to_datetime(uint32_t days, DateTime* datetime, uint16_t start_year) { uint32_t timestamp = days * 24 * 60 * 60; DateTime start_datetime = {0}; diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.h b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h index 2dc469c45..e8cbd7a37 100644 --- a/applications/main/nfc/api/mosgortrans/mosgortrans_util.h +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h @@ -10,6 +10,11 @@ extern "C" { #endif +void render_section_header( + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt); bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result); #ifdef __cplusplus diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h index bf0e926ee..790fa5766 100644 --- a/applications/main/nfc/api/nfc_app_api_table_i.h +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -15,4 +15,11 @@ static constexpr auto nfc_app_api_table = sort(create_array_t( API_METHOD( mosgortrans_parse_transport_block, bool, - (const MfClassicBlock* block, FuriString* result)))); + (const MfClassicBlock* block, FuriString* result)), + API_METHOD( + render_section_header, + void, + (FuriString * str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt)))); diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 36f745697..c38140de2 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -1,12 +1,24 @@ #include "nfc_supported_card_plugin.h" -#include +#include + +#include +#include +#include #include -#include - #define TAG "Plantain" +void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t start_year) { + uint32_t timestamp = minutes * 60; + DateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += datetime_datetime_to_timestamp(&start_datetime); + datetime_timestamp_to_datetime(timestamp, datetime); +} + typedef struct { uint64_t a; uint64_t b; @@ -207,29 +219,92 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != cfg.keys[cfg.data_sector].a) break; - // Point to block 0 of sector 4, value 0 - const uint8_t* temp_ptr = data->block[16].data; - // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t - // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal - uint32_t balance = - ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; - // Read card number - // Point to block 0 of sector 0, value 0 - temp_ptr = data->block[0].data; - // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal - uint8_t card_number_arr[7]; - for(size_t i = 0; i < 7; i++) { - card_number_arr[i] = temp_ptr[6 - i]; - } - // Copy card number to uint64_t + furi_string_printf(parsed_data, "\e#Plantain card\n"); uint64_t card_number = 0; for(size_t i = 0; i < 7; i++) { - card_number = (card_number << 8) | card_number_arr[i]; + card_number = (card_number << 8) | data->block[0].data[6 - i]; } - furi_string_printf( - parsed_data, "\e#Plantain\nNo.: %lluX\nBalance: %lu\n", card_number, balance); + // Print card number with 4-digit groups + furi_string_cat_printf(parsed_data, "Number: "); + FuriString* card_number_s = furi_string_alloc(); + furi_string_cat_printf(card_number_s, "%llu", card_number); + FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 "); + for(uint8_t i = 0; i < 24; i += 4) { + for(uint8_t j = 0; j < 4; j++) { + furi_string_push_back(tmp_s, furi_string_get_char(card_number_s, i + j)); + } + furi_string_push_back(tmp_s, ' '); + } + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tmp_s)); + if(data->type == MfClassicType1k) { + //balance + uint32_t balance = 0; + for(uint8_t i = 0; i < 4; i++) { + balance = (balance << 8) | data->block[16].data[3 - i]; + } + furi_string_cat_printf(parsed_data, "Balance: %ld rub\n", balance / 100); + + //trips + uint8_t trips_metro = data->block[21].data[0]; + uint8_t trips_ground = data->block[21].data[1]; + furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground); + //trip time + uint32_t last_trip_timestamp = 0; + for(uint8_t i = 0; i < 3; i++) { + last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i]; + } + DateTime last_trip = {0}; + from_minutes_to_datetime(last_trip_timestamp + 24 * 60, &last_trip, 2010); + furi_string_cat_printf( + parsed_data, + "Trip start: %02d.%02d.%04d %02d:%02d\n", + last_trip.day, + last_trip.month, + last_trip.year, + last_trip.hour, + last_trip.minute); + //validator + uint16_t validator = (data->block[20].data[5] << 8) | data->block[20].data[4]; + furi_string_cat_printf(parsed_data, "Validator: %d\n", validator); + //tariff + uint16_t fare = (data->block[20].data[7] << 8) | data->block[20].data[6]; + furi_string_cat_printf(parsed_data, "Tariff: %d rub\n", fare / 100); + //trips in metro + furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro); + //trips on ground + furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground); + //last payment + uint32_t last_payment_timestamp = 0; + for(uint8_t i = 0; i < 3; i++) { + last_payment_timestamp = (last_payment_timestamp << 8) | + data->block[18].data[4 - i]; + } + DateTime last_payment_date = {0}; + from_minutes_to_datetime(last_payment_timestamp + 24 * 60, &last_payment_date, 2010); + furi_string_cat_printf( + parsed_data, + "Last pay: %02d.%02d.%04d %02d:%02d\n", + last_payment_date.day, + last_payment_date.month, + last_payment_date.year, + last_payment_date.hour, + last_payment_date.minute); + //payment summ + uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8]; + furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100); + furi_string_free(card_number_s); + furi_string_free(tmp_s); + } else if(data->type == MfClassicType4k) { + //trips + uint8_t trips_metro = data->block[36].data[0]; + uint8_t trips_ground = data->block[36].data[1]; + furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground); + //trips in metro + furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro); + //trips on ground + furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground); + } parsed = true; } while(false); diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index cce74946d..ed2ee6c1d 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -1,10 +1,13 @@ #include "nfc_supported_card_plugin.h" -#include +#include +#include + +#include +#include #include - -#include -#include +#include "../../api/mosgortrans/mosgortrans_util.h" +#include "furi_hal_rtc.h" #define TAG "Social_Moscow" @@ -37,984 +40,48 @@ static const MfClassicKeyPair social_moscow_1k_keys[] = { {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}}; static const MfClassicKeyPair social_moscow_4k_keys[] = { - {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, {.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, - {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, - {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, {.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, - {.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, {.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, - {.a = 0x40ead80721ce, .b = 0x100533b89331}, {.a = 0x0db5e6523f7c, .b = 0x653a87594079}, - {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, - {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, - {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, - {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, - {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, - {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, - {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, - {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, - {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, - {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, - {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, - {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, - {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, - {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, - {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //1 + {.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, //2 + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //3 + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //4 + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, //5 + {.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, //6 + {.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, //7 + {.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, //8 + {.a = 0x40ead80721ce, .b = 0x100533b89331}, //9 + {.a = 0x0db5e6523f7c, .b = 0x653a87594079}, //10 + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //11 + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //12 + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //13 + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //14 + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //15 + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //16 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //17 + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //18 + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //19 + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //20 + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //21 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //22 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //23 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //24 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //25 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //26 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //27 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //28 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //29 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //30 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //31 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //32 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //33 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //34 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //35 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //36 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //37 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //38 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //39 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //40 }; -#define TOPBIT(X) (1 << ((X) - 1)) - -void from_days_to_datetime(uint16_t days, DateTime* datetime, uint16_t start_year) { - uint32_t timestamp = days * 24 * 60 * 60; - DateTime start_datetime = {0}; - start_datetime.year = start_year - 1; - start_datetime.month = 12; - start_datetime.day = 31; - timestamp += datetime_datetime_to_timestamp(&start_datetime); - datetime_timestamp_to_datetime(timestamp, datetime); -} - -void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t start_year) { - uint32_t timestamp = minutes * 60; - DateTime start_datetime = {0}; - start_datetime.year = start_year - 1; - start_datetime.month = 12; - start_datetime.day = 31; - timestamp += datetime_datetime_to_timestamp(&start_datetime); - datetime_timestamp_to_datetime(timestamp, datetime); -} - -bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { - uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); - - FURI_LOG_D(TAG, "Transport departament: %x", transport_departament); - - uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); - if(layout_type == 0xE) { - layout_type = bit_lib_get_bits_16(block->data, 52, 9); - } else if(layout_type == 0xF) { - layout_type = bit_lib_get_bits_16(block->data, 52, 14); - } - - FURI_LOG_D(TAG, "Layout type %x", layout_type); - - uint16_t card_view = 0; - uint16_t card_type = 0; - uint32_t card_number = 0; - uint8_t card_layout = 0; - uint8_t card_layout2 = 0; - uint16_t card_use_before_date = 0; - uint16_t card_blank_type = 0; - uint32_t card_start_trip_minutes = 0; - uint8_t card_minutes_pass = 0; - uint32_t card_remaining_funds = 0; - uint16_t card_validator = 0; - uint8_t card_blocked = 0; - uint32_t card_hash = 0; - - switch(layout_type) { - case 0x02: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 - uint8_t card_benefit_code = bit_lib_get_bits(block->data, 72, 8); //124 - uint32_t card_rfu1 = bit_lib_get_bits_32(block->data, 80, 32); //rfu1 - uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 - card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 - uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 177, 12); //403 - uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 189, 16); //402 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 - uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 - uint8_t card_start_trip_seconds = bit_lib_get_bits(block->data, 189, 6); //406 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 - uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 - uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 - uint16_t card_use_with_date = bit_lib_get_bits_16(block->data, 189, 16); //205 - uint8_t card_route = bit_lib_get_bits(block->data, 205, 1); //424 - uint16_t card_validator1 = bit_lib_get_bits_16(block->data, 206, 15); //422.1 - card_validator = bit_lib_get_bits_16(block->data, 205, 16); //422 - uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 221, 16); //331 - uint8_t card_write_enabled = bit_lib_get_bits(block->data, 237, 1); //write_enabled - uint8_t card_rfu2 = bit_lib_get_bits(block->data, 238, 2); //rfu2 - uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", - card_view, - card_type, - card_number, - card_use_before_date, - card_benefit_code, - card_rfu1, - card_crc16, - card_blocked, - card_start_trip_time, - card_start_trip_date, - card_valid_from_date, - card_valid_by_date, - card_start_trip_seconds, - card_transport_type1, - card_transport_type2, - card_transport_type3, - card_transport_type4, - card_use_with_date, - card_route, - card_validator1, - card_validator, - card_total_trips, - card_write_enabled, - card_rfu2, - card_crc16_2); - break; - } - case 0x06: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 - uint8_t card_geozone_a = bit_lib_get_bits(block->data, 72, 4); //GeoZoneA - uint8_t card_geozone_b = bit_lib_get_bits(block->data, 76, 4); //GeoZoneB - card_blank_type = bit_lib_get_bits_16(block->data, 80, 10); //121. - uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 90, 10); //122 - uint32_t card_rfu1 = bit_lib_get_bits_16(block->data, 100, 12); //rfu1 - uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 - card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 - uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 129, 12); //403 - uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 141, 16); //402 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 - uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 - uint16_t card_company = bit_lib_get_bits(block->data, 189, 4); //Company - uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 4); //422.1 - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 197, 10); //321 - uint8_t card_units = bit_lib_get_bits(block->data, 207, 6); //Units - uint16_t card_validator2 = bit_lib_get_bits_16(block->data, 213, 10); //422.2 - uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 223, 16); //331 - uint8_t card_extended = bit_lib_get_bits(block->data, 239, 1); //123 - uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x", - card_view, - card_type, - card_number, - card_use_before_date, - card_geozone_a, - card_geozone_b, - card_blank_type, - card_type_of_extended, - card_rfu1, - card_crc16, - card_blocked, - card_start_trip_time, - card_start_trip_date, - card_valid_from_date, - card_valid_by_date, - card_company, - card_validator1, - card_remaining_trips, - card_units, - card_validator2, - card_total_trips, - card_extended, - card_crc16_2); - card_validator = card_validator1 * 1024 + card_validator2; - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_valid_by_date, &card_use_before_date_s, 1992); - - DateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - (card_start_trip_date) * 24 * 60 + card_start_trip_time, - &card_start_trip_minutes_s, - 1992); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d of %d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_remaining_trips, - card_total_trips, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x08: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 - uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 - uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 - uint8_t card_rfu2 = bit_lib_get_bits(block->data, 153, 7); //rfu2 - uint8_t card_remaining_trips1 = bit_lib_get_bits(block->data, 160, 8); //321.1 - uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 168, 8); //321 - uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 2); //422.1 - uint16_t card_validator = bit_lib_get_bits_16(block->data, 177, 15); //422 - card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 - uint32_t card_rfu3 = bit_lib_get_bits_32(block->data, 224, 32); //rfu3 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %lx %x %lx", - card_view, - card_type, - card_number, - card_use_before_date, - card_rfu1, - card_valid_from_date, - card_valid_for_days, - card_requires_activation, - card_rfu2, - card_remaining_trips1, - card_remaining_trips, - card_validator1, - card_validator, - card_hash, - card_valid_from_date, - card_rfu3); - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); - - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_remaining_trips, - card_validator); - break; - } - case 0x0A: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 64, 12); //311 - uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 76, 19); //314 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 95, 1); //301 - card_start_trip_minutes = bit_lib_get_bits_32(block->data, 96, 19); //405 - card_minutes_pass = bit_lib_get_bits(block->data, 119, 7); //412 - uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 126, 2); //421.0 - uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 128, 8); //321 - uint16_t card_validator = bit_lib_get_bits_16(block->data, 136, 16); //422 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 152, 2); //421.1 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 154, 2); //421.2 - uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 156, 2); //421.3 - uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 158, 2); //421.4 - card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %lx %x %lx %x %x %x %x %x %x %x %x %lx", - card_view, - card_type, - card_number, - card_use_before_date, - card_valid_from_date, - card_valid_for_minutes, - card_requires_activation, - card_start_trip_minutes, - card_minutes_pass, - card_transport_type_flag, - card_remaining_trips, - card_validator, - card_transport_type1, - card_transport_type2, - card_transport_type3, - card_transport_type4, - card_hash); - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); - - DateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 2016); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_remaining_trips, - card_validator); - break; - } - case 0x0C: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 - uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 - uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 - uint16_t card_rfu2 = bit_lib_get_bits_16(block->data, 153, 13); //rfu2 - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 - uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 - card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 - uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 - uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 - uint8_t card_transport_type = bit_lib_get_bits(block->data, 251, 2); //421 - uint8_t card_rfu3 = bit_lib_get_bits(block->data, 253, 2); //rfu3 - uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %x %x %x", - card_view, - card_type, - card_number, - card_use_before_date, - card_rfu1, - card_valid_from_date, - card_valid_for_days, - card_requires_activation, - card_rfu2, - card_remaining_trips, - card_validator, - card_start_trip_date, - card_start_trip_time, - card_transport_type, - card_rfu3, - card_transfer_in_metro); - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); - DateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, - &card_start_trip_minutes_s, - 1992); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_remaining_trips, - card_validator); - break; - } - case 0x0D: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - uint8_t card_rfu1 = bit_lib_get_bits(block->data, 56, 8); //rfu1 - card_use_before_date = bit_lib_get_bits_16(block->data, 64, 16); //202 - uint16_t card_valid_for_time = bit_lib_get_bits_16(block->data, 80, 11); //316 - uint8_t card_rfu2 = bit_lib_get_bits(block->data, 91, 5); //rfu2 - uint16_t card_use_before_date2 = bit_lib_get_bits_16(block->data, 96, 16); //202.2 - uint16_t card_valid_for_time2 = bit_lib_get_bits_16(block->data, 123, 11); //316.2 - uint8_t card_rfu3 = bit_lib_get_bits(block->data, 123, 5); //rfu3 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 - uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 - uint8_t card_rfu4 = bit_lib_get_bits(block->data, 153, 2); //rfu4 - uint8_t card_passage_5_minutes = bit_lib_get_bits(block->data, 155, 5); //413 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 160, 2); //421.1 - uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 162, 1); //431 - uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 163, 3); //433 - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 - uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 - card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 - uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 - uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 251, 2); //421.2 - uint8_t card_rfu5 = bit_lib_get_bits(block->data, 253, 2); //rfu5 - uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", - card_view, - card_type, - card_number, - card_layout, - card_rfu1, - card_use_before_date, - card_valid_for_time, - card_rfu2, - card_use_before_date2, - card_valid_for_time2, - card_rfu3, - card_valid_from_date, - card_valid_for_days, - card_requires_activation, - card_rfu4, - card_passage_5_minutes, - card_transport_type1, - card_passage_in_metro, - card_passages_ground_transport, - card_remaining_trips, - card_validator, - card_start_trip_date, - card_start_trip_time, - card_transport_type2, - card_rfu5, - card_transfer_in_metro); - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); - DateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, - &card_start_trip_minutes_s, - 1992); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_remaining_trips, - card_validator); - break; - } - case 0x1C1: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202. - card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121. - card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 - uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 144, 16); //402 - uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 160, 11); //403 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 171, 2); //421.1 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 173, 2); //421.2 - uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 177, 1); //432 - uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 178, 1); //431 - uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 179, 3); //433 - card_minutes_pass = bit_lib_get_bits(block->data, 185, 8); //412. - card_remaining_funds = bit_lib_get_bits_32(block->data, 196, 19) / 100; //322 - uint8_t card_fare_trip = bit_lib_get_bits(block->data, 215, 2); //441 - card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 - uint8_t card_zoo = bit_lib_get_bits(block->data, 218, 1); //zoo - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %lx %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_use_before_date, - card_blank_type, - card_validator, - card_start_trip_date, - card_start_trip_time, - card_transport_type1, - card_transport_type2, - card_transfer_in_metro, - card_passage_in_metro, - card_passages_ground_transport, - card_minutes_pass, - card_remaining_funds, - card_fare_trip, - card_blocked, - card_zoo, - card_hash); - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); - - DateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 1992); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x1C2: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 - card_use_before_date = bit_lib_get_bits_16(block->data, 71, 16); //202. - card_blank_type = bit_lib_get_bits_16(block->data, 87, 10); //121. - uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 97, 16); //311 - uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 113, 9); //302 - uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 131, 20); //314 - card_minutes_pass = bit_lib_get_bits(block->data, 154, 8); //412. - uint8_t card_transport_type = bit_lib_get_bits(block->data, 163, 2); //421 - uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 165, 1); //431 - uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 166, 1); //432 - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 167, 10); //321 - card_validator = bit_lib_get_bits_16(block->data, 177, 16); //422 - uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 196, 20); //404 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 216, 1); //301 - card_blocked = bit_lib_get_bits(block->data, 217, 1); //303 - uint8_t card_extended = bit_lib_get_bits(block->data, 218, 1); //123 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %lx %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_type_of_extended, - card_use_before_date, - card_blank_type, - card_valid_to_date, - card_activate_during, - card_valid_for_minutes, - card_minutes_pass, - card_transport_type, - card_passage_in_metro, - card_transfer_in_metro, - card_remaining_trips, - card_validator, - card_start_trip_neg_minutes, - card_requires_activation, - card_blocked, - card_extended, - card_hash); - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); - - DateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - (card_valid_to_date - 1) * 24 * 60 + card_valid_for_minutes - - card_start_trip_neg_minutes, - &card_start_trip_minutes_s, - 2016); //-time - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x1C3: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202 - card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121 - card_remaining_funds = bit_lib_get_bits_32(block->data, 188, 22) / 100; //322 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 - card_start_trip_minutes = bit_lib_get_bits_32(block->data, 144, 23); //405 - uint8_t card_fare_trip = bit_lib_get_bits(block->data, 210, 2); //441 - card_minutes_pass = bit_lib_get_bits(block->data, 171, 7); //412 - uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 - uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 - uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 - card_blocked = bit_lib_get_bits(block->data, 212, 1); //303 - FURI_LOG_D( - TAG, - "Card view: %x, type: %x, number: %lx, layout: %x, layout2: %x, use before date: %x, blank type: %x, remaining funds: %lx, hash: %lx, validator: %x, start trip minutes: %lx, fare trip: %x, minutes pass: %x, transport type flag: %x, transport type1: %x, transport type2: %x, transport type3: %x, transport type4: %x, blocked: %x", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_use_before_date, - card_blank_type, - card_remaining_funds, - card_hash, - card_validator, - card_start_trip_minutes, - card_fare_trip, - card_minutes_pass, - card_transport_type_flag, - card_transport_type1, - card_transport_type2, - card_transport_type3, - card_transport_type4, - card_blocked); - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); - - DateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2016); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_remaining_funds, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x1C4: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 - card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. - card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. - uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 94, 13); //311 - uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 107, 9); //302 - uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 116, 10); //304 - uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 - card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. - uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 - uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 - uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 169, 10); //321 - card_validator = bit_lib_get_bits_16(block->data, 179, 16); //422 - uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 195, 20); //404 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 215, 1); //301 - card_blocked = bit_lib_get_bits(block->data, 216, 1); //303 - uint8_t card_extended = bit_lib_get_bits(block->data, 217, 1); //123 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_type_of_extended, - card_use_before_date, - card_blank_type, - card_valid_to_date, - card_activate_during, - card_extension_counter, - card_valid_for_minutes, - card_minutes_pass, - card_transport_type_flag, - card_transport_type1, - card_transport_type2, - card_transport_type3, - card_transport_type4, - card_remaining_trips, - card_validator, - card_start_trip_neg_minutes, - card_requires_activation, - card_blocked, - card_extended, - card_hash); - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2016); - - DateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - (card_use_before_date + 1) * 24 * 60 + card_valid_for_minutes - - card_start_trip_neg_minutes, - &card_start_trip_minutes_s, - 2011); //-time - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x1C5: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - card_use_before_date = bit_lib_get_bits_16(block->data, 61, 13); //202. - card_blank_type = bit_lib_get_bits_16(block->data, 74, 10); //121. - uint32_t card_valid_to_time = bit_lib_get_bits_32(block->data, 84, 23); //317 - uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 107, 10); //304 - card_start_trip_minutes = bit_lib_get_bits_32(block->data, 128, 23); //405 - uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 151, 7); //414 - card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. - card_remaining_funds = bit_lib_get_bits_32(block->data, 167, 19) / 100; //322 - card_validator = bit_lib_get_bits_16(block->data, 186, 16); //422 - card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 - uint16_t card_route = bit_lib_get_bits_16(block->data, 204, 12); //424 - uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 216, 7); //433 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %lx %x %lx %x %x %lx %x %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_use_before_date, - card_blank_type, - card_valid_to_time, - card_extension_counter, - card_start_trip_minutes, - card_metro_ride_with, - card_minutes_pass, - card_remaining_funds, - card_validator, - card_blocked, - card_route, - card_passages_ground_transport, - card_hash); - DateTime card_use_before_date_s = {0}; - - from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2019); - - DateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - card_start_trip_minutes - (24 * 60), &card_start_trip_minutes_s, 2019); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_remaining_funds, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x1C6: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 - card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. - card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. - uint32_t card_valid_from_date = bit_lib_get_bits_32(block->data, 94, 23); //311 - uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 117, 10); //304 - uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 - uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 148, 20); //404 - uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 168, 7); //414 - card_minutes_pass = bit_lib_get_bits(block->data, 175, 7); //412. - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 182, 7); //321 - card_validator = bit_lib_get_bits_16(block->data, 189, 16); //422 - card_blocked = bit_lib_get_bits(block->data, 205, 1); //303 - uint8_t card_extended = bit_lib_get_bits(block->data, 206, 1); //123 - uint16_t card_route = bit_lib_get_bits_16(block->data, 212, 12); //424 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %lx %x %lx %lx %x %x %x %x %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_type_of_extended, - card_use_before_date, - card_blank_type, - card_valid_from_date, - card_extension_counter, - card_valid_for_minutes, - card_start_trip_neg_minutes, - card_metro_ride_with, - card_minutes_pass, - card_remaining_trips, - card_validator, - card_blocked, - card_extended, - card_route, - card_hash); - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2019); - - DateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - card_valid_from_date + card_valid_for_minutes - card_start_trip_neg_minutes - 24 * 60, - &card_start_trip_minutes_s, - 2019); //-time - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x3CCB: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code - uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 - uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 - uint8_t card_interval = bit_lib_get_bits(block->data, 98, 4); //interval - uint16_t card_app_code1 = bit_lib_get_bits_16(block->data, 102, 16); //app_code1 - uint16_t card_hash1 = bit_lib_get_bits_16(block->data, 112, 16); //502.1 - uint16_t card_type1 = bit_lib_get_bits_16(block->data, 128, 10); //type1 - uint16_t card_app_code2 = bit_lib_get_bits_16(block->data, 138, 10); //app_code2 - uint16_t card_type2 = bit_lib_get_bits_16(block->data, 148, 10); //type2 - uint16_t card_app_code3 = bit_lib_get_bits_16(block->data, 158, 10); //app_code3 - uint16_t card_type3 = bit_lib_get_bits_16(block->data, 148, 10); //type3 - uint16_t card_app_code4 = bit_lib_get_bits_16(block->data, 168, 10); //app_code4 - uint16_t card_type4 = bit_lib_get_bits_16(block->data, 178, 10); //type4 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502.2 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_tech_code, - card_use_before_date, - card_blank_type, - card_valid_to_minutes, - card_valid_by_date, - card_interval, - card_app_code1, - card_hash1, - card_type1, - card_app_code2, - card_type2, - card_app_code3, - card_type3, - card_app_code4, - card_type4, - card_hash); - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); - - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_validator); - break; - } - case 0x3C0B: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code - uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 - uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 - uint16_t card_hash = bit_lib_get_bits_16(block->data, 112, 16); //502.1 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x", - card_view, - card_type, - card_number, - card_layout, - card_tech_code, - card_use_before_date, - card_blank_type, - card_valid_to_minutes, - card_valid_by_date, - card_hash); - DateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); - - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_validator); - break; - } - default: - return false; - } - - return true; -} - static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) { bool success = true; if(type == MfClassicType1k) { @@ -1104,6 +171,44 @@ static bool social_moscow_read(Nfc* nfc, NfcDevice* device) { return is_read; } +static uint8_t calculate_luhn(uint64_t number) { + // https://en.wikipedia.org/wiki/Luhn_algorithm + // Drop existing check digit to form payload + uint64_t payload = number / 10; + int sum = 0; + int position = 0; + + while(payload > 0) { + int digit = payload % 10; + if(position % 2 == 0) { + digit *= 2; + } + if(digit > 9) { + digit = (digit / 10) + (digit % 10); + } + sum += digit; + payload /= 10; + position++; + } + + return (10 - (sum % 10)) % 10; +} + +static uint64_t hex_num(uint64_t hex) { + uint64_t result = 0; + for(uint8_t i = 0; i < 8; ++i) { + uint8_t half_byte = hex & 0x0F; + uint64_t num = 0; + for(uint8_t j = 0; j < 4; ++j) { + num += (half_byte & 0x1) * (1 << j); + half_byte = half_byte >> 1; + } + result += num * pow(10, i); + hex = hex >> 4; + } + return result; +} + static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data) { furi_assert(device); @@ -1134,10 +239,19 @@ static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data uint8_t year = data->block[60].data[11]; uint8_t month = data->block[60].data[12]; + uint64_t number = hex_num(card_control) + hex_num(card_number) * 10 + + hex_num(card_region) * 10 * 10000000000 + + hex_num(card_code) * 10 * 10000000000 * 100; + + uint8_t luhn = calculate_luhn(number); + if(luhn != card_control) break; + FuriString* metro_result = furi_string_alloc(); FuriString* ground_result = furi_string_alloc(); - bool result1 = parse_transport_block(&data->block[4], metro_result); - bool result2 = parse_transport_block(&data->block[16], ground_result); + bool is_metro_data_present = + mosgortrans_parse_transport_block(&data->block[4], metro_result); + bool is_ground_data_present = + mosgortrans_parse_transport_block(&data->block[16], ground_result); furi_string_cat_printf( parsed_data, "\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n", @@ -1150,17 +264,17 @@ static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data year, data->block[60].data[13], data->block[60].data[14]); - if(result1) { - furi_string_cat_printf( - parsed_data, "\e#Metro\n%s\n", furi_string_get_cstr(metro_result)); + if(is_metro_data_present && !furi_string_empty(metro_result)) { + render_section_header(parsed_data, "Metro", 22, 21); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result)); } - if(result2) { - furi_string_cat_printf( - parsed_data, "\e#Ground\n%s\n", furi_string_get_cstr(ground_result)); + if(is_ground_data_present && !furi_string_empty(ground_result)) { + render_section_header(parsed_data, "Ground", 21, 20); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result)); } furi_string_free(ground_result); furi_string_free(metro_result); - parsed = result1 || result2; + parsed = true; } while(false); return parsed; diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 0c93fa59a..bd36d40e5 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -82,20 +82,6 @@ static const MfClassicKeyPair troika_4k_keys[] = { {.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40 }; -static void troika_render_section_header( - FuriString* str, - const char* name, - uint8_t prefix_separator_cnt, - uint8_t suffix_separator_cnt) { - for(uint8_t i = 0; i < prefix_separator_cnt; i++) { - furi_string_cat_printf(str, ":"); - } - furi_string_cat_printf(str, "[ %s ]", name); - for(uint8_t i = 0; i < suffix_separator_cnt; i++) { - furi_string_cat_printf(str, ":"); - } -} - static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { bool success = true; @@ -212,23 +198,25 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { FuriString* ground_result = furi_string_alloc(); FuriString* tat_result = furi_string_alloc(); - bool result1 = mosgortrans_parse_transport_block(&data->block[32], metro_result); - bool result2 = mosgortrans_parse_transport_block(&data->block[28], ground_result); - bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result); + bool is_metro_data_present = + mosgortrans_parse_transport_block(&data->block[32], metro_result); + bool is_ground_data_present = + mosgortrans_parse_transport_block(&data->block[28], ground_result); + bool is_tat_data_present = mosgortrans_parse_transport_block(&data->block[16], tat_result); furi_string_cat_printf(parsed_data, "\e#Troyka card\n"); - if(result1 && !furi_string_empty(metro_result)) { - troika_render_section_header(parsed_data, "Metro", 22, 21); + if(is_metro_data_present && !furi_string_empty(metro_result)) { + render_section_header(parsed_data, "Metro", 22, 21); furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result)); } - if(result2 && !furi_string_empty(ground_result)) { - troika_render_section_header(parsed_data, "Ediny", 22, 22); + if(is_ground_data_present && !furi_string_empty(ground_result)) { + render_section_header(parsed_data, "Ediny", 22, 22); furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result)); } - if(result3 && !furi_string_empty(tat_result)) { - troika_render_section_header(parsed_data, "TAT", 24, 23); + if(is_tat_data_present && !furi_string_empty(tat_result)) { + render_section_header(parsed_data, "TAT", 24, 23); furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tat_result)); } @@ -236,7 +224,7 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_free(ground_result); furi_string_free(metro_result); - parsed = result1 || result2 || result3; + parsed = is_metro_data_present || is_ground_data_present || is_tat_data_present; } while(false); return parsed; diff --git a/lib/drivers/bq27220.c b/lib/drivers/bq27220.c index a3a88603d..d60e287da 100644 --- a/lib/drivers/bq27220.c +++ b/lib/drivers/bq27220.c @@ -1,29 +1,77 @@ - #include "bq27220.h" #include "bq27220_reg.h" #include "bq27220_data_memory.h" -_Static_assert(sizeof(BQ27220DMGaugingConfig) == 2, "Incorrect structure size"); - #include #include #define TAG "Gauge" -static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { - uint16_t buf = 0; +#define BQ27220_ID (0x0220u) - furi_hal_i2c_read_mem( - handle, BQ27220_ADDRESS, address, (uint8_t*)&buf, 2, BQ27220_I2C_TIMEOUT); +/** Delay between 2 writes into Subclass/MAC area. Fails at ~120us. */ +#define BQ27220_MAC_WRITE_DELAY_US (250u) - return buf; +/** Delay between we ask chip to load data to MAC and it become valid. Fails at ~500us. */ +#define BQ27220_SELECT_DELAY_US (1000u) + +/** Delay between 2 control operations(like unseal or full access). Fails at ~2500us.*/ +#define BQ27220_MAGIC_DELAY_US (5000u) + +/** Delay before freshly written configuration can be read. Fails at ? */ +#define BQ27220_CONFIG_DELAY_US (10000u) + +/** Config apply delay. Must wait, or DM read returns garbage. */ +#define BQ27220_CONFIG_APPLY_US (2000000u) + +/** Timeout for common operations. */ +#define BQ27220_TIMEOUT_COMMON_US (2000000u) + +/** Timeout for reset operation. Normally reset takes ~2s. */ +#define BQ27220_TIMEOUT_RESET_US (4000000u) + +/** Timeout cycle interval */ +#define BQ27220_TIMEOUT_CYCLE_INTERVAL_US (1000u) + +/** Timeout cycles count helper */ +#define BQ27220_TIMEOUT(timeout_us) ((timeout_us) / (BQ27220_TIMEOUT_CYCLE_INTERVAL_US)) + +#ifdef BQ27220_DEBUG +#define BQ27220_DEBUG_LOG(...) FURI_LOG_D(TAG, ##__VA_ARGS__) +#else +#define BQ27220_DEBUG_LOG(...) +#endif + +static inline bool bq27220_read_reg( + FuriHalI2cBusHandle* handle, + uint8_t address, + uint8_t* buffer, + size_t buffer_size) { + return furi_hal_i2c_trx( + handle, BQ27220_ADDRESS, &address, 1, buffer, buffer_size, BQ27220_I2C_TIMEOUT); } -static bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) { - bool ret = furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandControl, (uint8_t*)&control, 2, BQ27220_I2C_TIMEOUT); +static inline bool bq27220_write( + FuriHalI2cBusHandle* handle, + uint8_t address, + const uint8_t* buffer, + size_t buffer_size) { + return furi_hal_i2c_write_mem( + handle, BQ27220_ADDRESS, address, buffer, buffer_size, BQ27220_I2C_TIMEOUT); +} - return ret; +static inline bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) { + return bq27220_write(handle, CommandControl, (uint8_t*)&control, 2); +} + +static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { + uint16_t buf = BQ27220_ERROR; + + if(!bq27220_read_reg(handle, address, (uint8_t*)&buf, 2)) { + FURI_LOG_E(TAG, "bq27220_read_word failed"); + } + + return buf; } static uint8_t bq27220_get_checksum(uint8_t* data, uint16_t len) { @@ -56,49 +104,49 @@ static bool bq27220_parameter_check( if(update) { // Datasheet contains incorrect procedure for memory update, more info: // https://e2e.ti.com/support/power-management-group/power-management/f/power-management-forum/719878/bq27220-technical-reference-manual-sluubd4-is-missing-extended-data-commands-chapter + // Also see note in the header - // 2. Write the address AND the parameter data to 0x3E+ (auto increment) - if(!furi_hal_i2c_write_mem( - handle, - BQ27220_ADDRESS, - CommandSelectSubclass, - buffer, - size + 2, - BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM write failed"); + // Write the address AND the parameter data to 0x3E+ (auto increment) + if(!bq27220_write(handle, CommandSelectSubclass, buffer, size + 2)) { + FURI_LOG_E(TAG, "DM write failed"); break; } - furi_delay_us(10000); + // We must wait, otherwise write will fail + furi_delay_us(BQ27220_MAC_WRITE_DELAY_US); - // 3. Calculate the check sum: 0xFF - (sum of address and data) OR 0xFF + // Calculate the check sum: 0xFF - (sum of address and data) OR 0xFF uint8_t checksum = bq27220_get_checksum(buffer, size + 2); - // 4. Write the check sum to 0x60 and the total length of (address + parameter data + check sum + length) to 0x61 + // Write the check sum to 0x60 and the total length of (address + parameter data + check sum + length) to 0x61 buffer[0] = checksum; // 2 bytes address, `size` bytes data, 1 byte check sum, 1 byte length buffer[1] = 2 + size + 1 + 1; - if(!furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandMACDataSum, buffer, 2, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "CRC write failed"); + if(!bq27220_write(handle, CommandMACDataSum, buffer, 2)) { + FURI_LOG_E(TAG, "CRC write failed"); break; } - - furi_delay_us(10000); + // Final wait as in gm.fs specification + furi_delay_us(BQ27220_CONFIG_DELAY_US); ret = true; } else { - if(!furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandSelectSubclass, buffer, 2, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM SelectSubclass for read failed"); + if(!bq27220_write(handle, CommandSelectSubclass, buffer, 2)) { + FURI_LOG_E(TAG, "DM SelectSubclass for read failed"); break; } - if(!furi_hal_i2c_rx(handle, BQ27220_ADDRESS, old_data, size, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM read failed"); + // bqstudio uses 15ms wait delay here + furi_delay_us(BQ27220_SELECT_DELAY_US); + + if(!bq27220_read_reg(handle, CommandMACData, old_data, size)) { + FURI_LOG_E(TAG, "DM read failed"); break; } + // bqstudio uses burst reads with continue(CommandSelectSubclass without argument) and ~5ms between burst + furi_delay_us(BQ27220_SELECT_DELAY_US); + if(*(uint32_t*)&(old_data[0]) != *(uint32_t*)&(buffer[2])) { - FURI_LOG_W( //-V641 + FURI_LOG_E( //-V641 TAG, "Data at 0x%04x(%zu): 0x%08lx!=0x%08lx", address, @@ -119,22 +167,34 @@ static bool bq27220_data_memory_check( const BQ27220DMData* data_memory, bool update) { if(update) { - if(!bq27220_control(handle, Control_ENTER_CFG_UPDATE)) { + const uint16_t cfg_request = Control_ENTER_CFG_UPDATE; + if(!bq27220_write( + handle, CommandSelectSubclass, (uint8_t*)&cfg_request, sizeof(cfg_request))) { FURI_LOG_E(TAG, "ENTER_CFG_UPDATE command failed"); return false; }; // Wait for enter CFG update mode - uint32_t timeout = 100; - OperationStatus status = {0}; - while((status.CFGUPDATE != true) && (timeout-- > 0)) { - bq27220_get_operation_status(handle, &status); + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else if(operation_status.CFGUPDATE) { + break; + }; + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); } if(timeout == 0) { - FURI_LOG_E(TAG, "CFGUPDATE mode failed"); + FURI_LOG_E( + TAG, + "Enter CFGUPDATE mode failed, CFG %u, SEC %u", + operation_status.CFGUPDATE, + operation_status.SEC); return false; } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); } // Process data memory records @@ -179,43 +239,283 @@ static bool bq27220_data_memory_check( } // Finalize configuration update - if(update) { + if(update && result) { bq27220_control(handle, Control_EXIT_CFG_UPDATE_REINIT); - furi_delay_us(10000); + + // Wait for gauge to apply new configuration + furi_delay_us(BQ27220_CONFIG_APPLY_US); + + // ensure that we exited config update mode + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else if(operation_status.CFGUPDATE != true) { + break; + } + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); + } + + // Check timeout + if(timeout == 0) { + FURI_LOG_E(TAG, "Exit CFGUPDATE mode failed"); + return false; + } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); } return result; } -bool bq27220_init(FuriHalI2cBusHandle* handle) { - // Request device number(chip PN) - if(!bq27220_control(handle, Control_DEVICE_NUMBER)) { - FURI_LOG_E(TAG, "Device is not present"); - return false; - }; - // Check control response - uint16_t data = 0; - data = bq27220_read_word(handle, CommandControl); - if(data != 0xFF00) { - FURI_LOG_E(TAG, "Invalid control response: %x", data); - return false; - }; +bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { + bool result = false; + bool reset_and_provisioning_required = false; - data = bq27220_read_word(handle, CommandMACData); - FURI_LOG_I(TAG, "Device Number %04x", data); + do { + // Request device number(chip PN) + BQ27220_DEBUG_LOG("Checking device ID"); + if(!bq27220_control(handle, Control_DEVICE_NUMBER)) { + FURI_LOG_E(TAG, "ID: Device is not responding"); + break; + }; + // Enterprise wait(MAC read fails if less than 500us) + // bqstudio uses ~15ms + furi_delay_us(BQ27220_SELECT_DELAY_US); + // Read id data from MAC scratch space + uint16_t data = bq27220_read_word(handle, CommandMACData); + if(data != BQ27220_ID) { + FURI_LOG_E(TAG, "Invalid Device Number %04x != 0x0220", data); + break; + } - return data == 0x0220; + // Unseal device since we are going to read protected configuration + BQ27220_DEBUG_LOG("Unsealing"); + if(!bq27220_unseal(handle)) { + break; + } + + // Try to recover gauge from forever init + BQ27220_DEBUG_LOG("Checking initialization status"); + Bq27220OperationStatus operation_status; + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Failed to get operation status"); + break; + } + if(!operation_status.INITCOMP || operation_status.CFGUPDATE) { + FURI_LOG_E(TAG, "Incorrect state, reset needed"); + reset_and_provisioning_required = true; + } + + // Ensure correct profile is selected + BQ27220_DEBUG_LOG("Checking chosen profile"); + Bq27220ControlStatus control_status; + if(!bq27220_get_control_status(handle, &control_status)) { + FURI_LOG_E(TAG, "Failed to get control status"); + break; + } + if(control_status.BATT_ID != 0) { + FURI_LOG_E(TAG, "Incorrect profile, reset needed"); + reset_and_provisioning_required = true; + } + + // Ensure correct configuration loaded into gauge DataMemory + // Only if reset is not required, otherwise we don't + if(!reset_and_provisioning_required) { + BQ27220_DEBUG_LOG("Checking data memory"); + if(!bq27220_data_memory_check(handle, data_memory, false)) { + FURI_LOG_E(TAG, "Incorrect configuration data, reset needed"); + reset_and_provisioning_required = true; + } + } + + // Reset needed + if(reset_and_provisioning_required) { + FURI_LOG_W(TAG, "Resetting device"); + if(!bq27220_reset(handle)) { + FURI_LOG_E(TAG, "Failed to reset device"); + break; + } + + // Get full access to read and modify parameters + // Also it looks like this step is totally unnecessary + BQ27220_DEBUG_LOG("Acquiring Full Access"); + if(!bq27220_full_access(handle)) { + break; + } + + // Update memory + FURI_LOG_W(TAG, "Updating data memory"); + bq27220_data_memory_check(handle, data_memory, true); + if(!bq27220_data_memory_check(handle, data_memory, false)) { + FURI_LOG_E(TAG, "Data memory update failed"); + break; + } + } + + BQ27220_DEBUG_LOG("Sealing"); + if(!bq27220_seal(handle)) { + FURI_LOG_E(TAG, "Seal failed"); + break; + } + + result = true; + } while(0); + + return result; } -bool bq27220_apply_data_memory(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { - FURI_LOG_I(TAG, "Verifying data memory"); - if(!bq27220_data_memory_check(handle, data_memory, false)) { - FURI_LOG_I(TAG, "Updating data memory"); - bq27220_data_memory_check(handle, data_memory, true); - } - FURI_LOG_I(TAG, "Data memory verification complete"); +bool bq27220_reset(FuriHalI2cBusHandle* handle) { + bool result = false; + do { + if(!bq27220_control(handle, Control_RESET)) { + FURI_LOG_E(TAG, "Reset request failed"); + break; + }; - return true; + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_RESET_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else if(operation_status.INITCOMP == true) { + break; + }; + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); + } + + if(timeout == 0) { + FURI_LOG_E(TAG, "INITCOMP timeout after reset"); + break; + } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); + + result = true; + } while(0); + + return result; +} + +bool bq27220_seal(FuriHalI2cBusHandle* handle) { + Bq27220OperationStatus operation_status = {0}; + bool result = false; + do { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC == Bq27220OperationStatusSecSealed) { + result = true; + break; + } + + if(!bq27220_control(handle, Control_SEALED)) { + FURI_LOG_E(TAG, "Seal request failed"); + break; + } + + furi_delay_us(BQ27220_SELECT_DELAY_US); + + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecSealed) { + FURI_LOG_E(TAG, "Seal failed"); + break; + } + + result = true; + } while(0); + + return result; +} + +bool bq27220_unseal(FuriHalI2cBusHandle* handle) { + Bq27220OperationStatus operation_status = {0}; + bool result = false; + do { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecSealed) { + result = true; + break; + } + + // Hai, Kazuma desu + bq27220_control(handle, UnsealKey1); + furi_delay_us(BQ27220_MAGIC_DELAY_US); + bq27220_control(handle, UnsealKey2); + furi_delay_us(BQ27220_MAGIC_DELAY_US); + + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecUnsealed) { + FURI_LOG_E(TAG, "Unseal failed %u", operation_status.SEC); + break; + } + + result = true; + } while(0); + + return result; +} + +bool bq27220_full_access(FuriHalI2cBusHandle* handle) { + bool result = false; + + do { + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else { + break; + }; + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); + } + + if(timeout == 0) { + FURI_LOG_E(TAG, "Failed to get operation status"); + break; + } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); + + // Already full access + if(operation_status.SEC == Bq27220OperationStatusSecFull) { + result = true; + break; + } + // Must be unsealed to get full access + if(operation_status.SEC != Bq27220OperationStatusSecUnsealed) { + FURI_LOG_E(TAG, "Not in unsealed state"); + break; + } + + // Explosion!!! + bq27220_control(handle, FullAccessKey); //-V760 + furi_delay_us(BQ27220_MAGIC_DELAY_US); + bq27220_control(handle, FullAccessKey); + furi_delay_us(BQ27220_MAGIC_DELAY_US); + + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecFull) { + FURI_LOG_E(TAG, "Full access failed %u", operation_status.SEC); + break; + } + + result = true; + } while(0); + + return result; } uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle) { @@ -226,24 +526,30 @@ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandCurrent); } -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status) { - uint16_t data = bq27220_read_word(handle, CommandBatteryStatus); - if(data == BQ27220_ERROR) { - return false; - } else { - *(uint16_t*)battery_status = data; - return true; - } +bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status) { + return bq27220_read_reg(handle, CommandControl, (uint8_t*)control_status, 2); } -bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status) { - uint16_t data = bq27220_read_word(handle, CommandOperationStatus); - if(data == BQ27220_ERROR) { +bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status) { + return bq27220_read_reg(handle, CommandBatteryStatus, (uint8_t*)battery_status, 2); +} + +bool bq27220_get_operation_status( + FuriHalI2cBusHandle* handle, + Bq27220OperationStatus* operation_status) { + return bq27220_read_reg(handle, CommandOperationStatus, (uint8_t*)operation_status, 2); +} + +bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status) { + // Request gauging data to be loaded to MAC + if(!bq27220_control(handle, Control_GAUGING_STATUS)) { + FURI_LOG_E(TAG, "DM SelectSubclass for read failed"); return false; - } else { - *(uint16_t*)operation_status = data; - return true; } + // Wait for data being loaded to MAC + furi_delay_us(BQ27220_SELECT_DELAY_US); + // Read id data from MAC scratch space + return bq27220_read_reg(handle, CommandMACData, (uint8_t*)gauging_status, 2); } uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle) { diff --git a/lib/drivers/bq27220.h b/lib/drivers/bq27220.h index fc76e318f..cdfcb20b1 100644 --- a/lib/drivers/bq27220.h +++ b/lib/drivers/bq27220.h @@ -1,3 +1,31 @@ +/** + * @file bq27220.h + * + * Quite problematic chip with quite bad documentation. + * + * Couple things to keep in mind: + * + * - Datasheet and technical reference manual are full of bullshit + * - bqstudio is ignoring them + * - bqstudio i2c exchange tracing gives some ideas on timings that works, but there is a catch + * - bqstudio timings contradicts to gm.fs file specification + * - it's impossible to reproduce all situations in bqstudio + * - experiments with blackbox can not cover all edge cases + * - final timings are kinda blend between all of those sources + * - device behavior differs depending on i2c clock speed + * - The Hero Himmel would not have used this gauge in the first place + * + * Couple advises if you'll need to modify this driver: + * - Reset and wait for INITCOMP if something is not right. + * - Do not do partial config update, it takes unpredictable amount of time to apply. + * - Don't forget to reset chip before writing new config. + * - If something fails at config update stage, wait for 4 seconds before doing next cycle. + * - If you can program and lock chip at factory stage - do it. It will save you a lot of time. + * - Keep sealed or strange things may happen. + * - There is a condition when it may stuck at INITCOMP state, just "press reset button". + * + */ + #pragma once #include @@ -9,26 +37,45 @@ typedef struct { // Low byte, Low bit first - bool DSG : 1; // The device is in DISCHARGE - bool SYSDWN : 1; // System down bit indicating the system should shut down - bool TDA : 1; // Terminate Discharge Alarm - bool BATTPRES : 1; // Battery Present detected - bool AUTH_GD : 1; // Detect inserted battery - bool OCVGD : 1; // Good OCV measurement taken - bool TCA : 1; // Terminate Charge Alarm - bool RSVD : 1; // Reserved + uint8_t BATT_ID : 3; /**< Battery Identification */ + bool SNOOZE : 1; /**< SNOOZE mode is enabled */ + bool BCA : 1; /**< fuel gauge board calibration routine is active */ + bool CCA : 1; /**< Coulomb Counter Calibration routine is active */ + uint8_t RSVD0 : 2; /**< Reserved */ // High byte, Low bit first - bool CHGINH : 1; // Charge inhibit - bool FC : 1; // Full-charged is detected - bool OTD : 1; // Overtemperature in discharge condition is detected - bool OTC : 1; // Overtemperature in charge condition is detected - bool SLEEP : 1; // Device is operating in SLEEP mode when set - bool OCVFAIL : 1; // Status bit indicating that the OCV reading failed due to current - bool OCVCOMP : 1; // An OCV measurement update is complete - bool FD : 1; // Full-discharge is detected -} BatteryStatus; + uint8_t RSVD1; /**< Reserved */ +} Bq27220ControlStatus; -_Static_assert(sizeof(BatteryStatus) == 2, "Incorrect structure size"); +_Static_assert(sizeof(Bq27220ControlStatus) == 2, "Incorrect Bq27220ControlStatus structure size"); + +typedef struct { + // Low byte, Low bit first + bool DSG : 1; /**< The device is in DISCHARGE */ + bool SYSDWN : 1; /**< System down bit indicating the system should shut down */ + bool TDA : 1; /**< Terminate Discharge Alarm */ + bool BATTPRES : 1; /**< Battery Present detected */ + bool AUTH_GD : 1; /**< Detect inserted battery */ + bool OCVGD : 1; /**< Good OCV measurement taken */ + bool TCA : 1; /**< Terminate Charge Alarm */ + bool RSVD : 1; /**< Reserved */ + // High byte, Low bit first + bool CHGINH : 1; /**< Charge inhibit */ + bool FC : 1; /**< Full-charged is detected */ + bool OTD : 1; /**< Overtemperature in discharge condition is detected */ + bool OTC : 1; /**< Overtemperature in charge condition is detected */ + bool SLEEP : 1; /**< Device is operating in SLEEP mode when set */ + bool OCVFAIL : 1; /**< Status bit indicating that the OCV reading failed due to current */ + bool OCVCOMP : 1; /**< An OCV measurement update is complete */ + bool FD : 1; /**< Full-discharge is detected */ +} Bq27220BatteryStatus; + +_Static_assert(sizeof(Bq27220BatteryStatus) == 2, "Incorrect Bq27220BatteryStatus structure size"); + +typedef enum { + Bq27220OperationStatusSecSealed = 0b11, + Bq27220OperationStatusSecUnsealed = 0b10, + Bq27220OperationStatusSecFull = 0b01, +} Bq27220OperationStatusSec; typedef struct { // Low byte, Low bit first @@ -40,53 +87,189 @@ typedef struct { bool SMTH : 1; /**< RemainingCapacity is scaled by smooth engine */ bool BTPINT : 1; /**< BTP threshold has been crossed */ // High byte, Low bit first - uint8_t RSVD1 : 2; + uint8_t RSVD1 : 2; /**< Reserved */ bool CFGUPDATE : 1; /**< Gauge is in CONFIG UPDATE mode */ - uint8_t RSVD0 : 5; -} OperationStatus; + uint8_t RSVD0 : 5; /**< Reserved */ +} Bq27220OperationStatus; -_Static_assert(sizeof(OperationStatus) == 2, "Incorrect structure size"); +_Static_assert( + sizeof(Bq27220OperationStatus) == 2, + "Incorrect Bq27220OperationStatus structure size"); + +typedef struct { + // Low byte, Low bit first + bool FD : 1; /**< Full Discharge */ + bool FC : 1; /**< Full Charge */ + bool TD : 1; /**< Terminate Discharge */ + bool TC : 1; /**< Terminate Charge */ + bool RSVD0 : 1; /**< Reserved */ + bool EDV : 1; /**< Cell voltage is above or below EDV0 threshold */ + bool DSG : 1; /**< DISCHARGE or RELAXATION */ + bool CF : 1; /**< Battery conditioning is needed */ + // High byte, Low bit first + uint8_t RSVD1 : 2; /**< Reserved */ + bool FCCX : 1; /**< fcc1hz clock going into CC: 0 = 1 Hz, 1 = 16 Hz*/ + uint8_t RSVD2 : 2; /**< Reserved */ + bool EDV1 : 1; /**< Cell voltage is above or below EDV1 threshold */ + bool EDV2 : 1; /**< Cell voltage is above or below EDV2 threshold */ + bool VDQ : 1; /**< Charge cycle FCC update qualification */ +} Bq27220GaugingStatus; + +_Static_assert(sizeof(Bq27220GaugingStatus) == 2, "Incorrect Bq27220GaugingStatus structure size"); typedef struct BQ27220DMData BQ27220DMData; /** Initialize Driver - * @return true on success, false otherwise + * + * This routine performs a lot of things under the hood: + * - Verifies that gauge is present on i2c bus and got correct ID(0220) + * - Unseals gauge + * - Checks various internal statuses + * - Checks that current profile is 0 + * - Checks configuration again provided data_memory + * - Reset gauge if something on previous stages was fishy + * - Updates configuration if needed + * - Sealing gauge to prevent configuration and state from accidental damage + * + * @param handle The I2C Bus handle + * @param[in] data_memory The data memory to be uploaded into gauge + * + * @return true on success, false otherwise */ -bool bq27220_init(FuriHalI2cBusHandle* handle); +bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory); -/** Initialize Driver - * @return true on success, false otherwise +/** Reset gauge + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise */ -bool bq27220_apply_data_memory(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory); +bool bq27220_reset(FuriHalI2cBusHandle* handle); -/** Get battery voltage in mV or error */ +/** Seal gauge access + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise + */ +bool bq27220_seal(FuriHalI2cBusHandle* handle); + +/** Unseal gauge access + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise + */ +bool bq27220_unseal(FuriHalI2cBusHandle* handle); + +/** Get full access + * + * @warning must be done in unsealed state + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise + */ +bool bq27220_full_access(FuriHalI2cBusHandle* handle); + +/** Get battery voltage + * + * @param handle The I2C Bus handle + * + * @return voltage in mV or BQ27220_ERROR + */ uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle); -/** Get current in mA or error*/ +/** Get current + * + * @param handle The I2C Bus handle + * + * @return current in mA or BQ27220_ERROR + */ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle); -/** Get battery status */ -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status); +/** Get control status + * + * @param handle The handle + * @param control_status The control status + * + * @return true on success, false otherwise + */ +bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status); -/** Get operation status */ -bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status); +/** Get battery status + * + * @param handle The handle + * @param battery_status The battery status + * + * @return true on success, false otherwise + */ +bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status); -/** Get temperature in units of 0.1°K */ +/** Get operation status + * + * @param handle The handle + * @param operation_status The operation status + * + * @return true on success, false otherwise + */ +bool bq27220_get_operation_status( + FuriHalI2cBusHandle* handle, + Bq27220OperationStatus* operation_status); + +/** Get gauging status + * + * @param handle The handle + * @param gauging_status The gauging status + * + * @return true on success, false otherwise + */ +bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status); + +/** Get temperature + * + * @param handle The I2C Bus handle + * + * @return temperature in units of 0.1°K + */ uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle); -/** Get compensated full charge capacity in in mAh */ +/** Get compensated full charge capacity + * + * @param handle The I2C Bus handle + * + * @return full charge capacity in mAh or BQ27220_ERROR + */ uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle); -/** Get design capacity in mAh */ +/** Get design capacity + * + * @param handle The I2C Bus handle + * + * @return design capacity in mAh or BQ27220_ERROR + */ uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle); -/** Get remaining capacity in in mAh */ +/** Get remaining capacity + * + * @param handle The I2C Bus handle + * + * @return remaining capacity in mAh or BQ27220_ERROR + */ uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle); -/** Get predicted remaining battery capacity in percents */ +/** Get predicted remaining battery capacity + * + * @param handle The I2C Bus handle + * + * @return state of charge in percents or BQ27220_ERROR + */ uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle); -/** Get ratio of full charge capacity over design capacity in percents */ +/** Get ratio of full charge capacity over design capacity + * + * @param handle The I2C Bus handle + * + * @return state of health in percents or BQ27220_ERROR + */ uint16_t bq27220_get_state_of_health(FuriHalI2cBusHandle* handle); - -void bq27220_change_design_capacity(FuriHalI2cBusHandle* handle, uint16_t capacity); diff --git a/lib/drivers/bq27220_data_memory.h b/lib/drivers/bq27220_data_memory.h index 30f2dae1e..0bd9348d2 100644 --- a/lib/drivers/bq27220_data_memory.h +++ b/lib/drivers/bq27220_data_memory.h @@ -82,3 +82,5 @@ typedef struct { const bool SME0 : 1; const uint8_t RSVD3 : 3; } BQ27220DMGaugingConfig; + +_Static_assert(sizeof(BQ27220DMGaugingConfig) == 2, "Incorrect structure size"); diff --git a/lib/drivers/bq27220_reg.h b/lib/drivers/bq27220_reg.h index 1c1ec9d8f..2d93e31d0 100644 --- a/lib/drivers/bq27220_reg.h +++ b/lib/drivers/bq27220_reg.h @@ -1,68 +1,76 @@ #pragma once -#define BQ27220_ADDRESS 0xAA -#define BQ27220_I2C_TIMEOUT 50 +#define BQ27220_ADDRESS (0xAAu) +#define BQ27220_I2C_TIMEOUT (50u) -#define CommandControl 0x00 -#define CommandAtRate 0x02 -#define CommandAtRateTimeToEmpty 0x04 -#define CommandTemperature 0x06 -#define CommandVoltage 0x08 -#define CommandBatteryStatus 0x0A -#define CommandCurrent 0x0C -#define CommandRemainingCapacity 0x10 -#define CommandFullChargeCapacity 0x12 -#define CommandAverageCurrent 0x14 -#define CommandTimeToEmpty 0x16 -#define CommandTimeToFull 0x18 -#define CommandStandbyCurrent 0x1A -#define CommandStandbyTimeToEmpty 0x1C -#define CommandMaxLoadCurrent 0x1E -#define CommandMaxLoadTimeToEmpty 0x20 -#define CommandRawCoulombCount 0x22 -#define CommandAveragePower 0x24 -#define CommandInternalTemperature 0x28 -#define CommandCycleCount 0x2A -#define CommandStateOfCharge 0x2C -#define CommandStateOfHealth 0x2E -#define CommandChargeVoltage 0x30 -#define CommandChargeCurrent 0x32 -#define CommandBTPDischargeSet 0x34 -#define CommandBTPChargeSet 0x36 -#define CommandOperationStatus 0x3A -#define CommandDesignCapacity 0x3C -#define CommandSelectSubclass 0x3E -#define CommandMACData 0x40 -#define CommandMACDataSum 0x60 -#define CommandMACDataLen 0x61 -#define CommandAnalogCount 0x79 -#define CommandRawCurrent 0x7A -#define CommandRawVoltage 0x7C -#define CommandRawIntTemp 0x7E +#define CommandControl (0x00u) +#define CommandAtRate (0x02u) +#define CommandAtRateTimeToEmpty (0x04u) +#define CommandTemperature (0x06u) +#define CommandVoltage (0x08u) +#define CommandBatteryStatus (0x0Au) +#define CommandCurrent (0x0Cu) +#define CommandRemainingCapacity (0x10u) +#define CommandFullChargeCapacity (0x12u) +#define CommandAverageCurrent (0x14u) +#define CommandTimeToEmpty (0x16u) +#define CommandTimeToFull (0x18u) +#define CommandStandbyCurrent (0x1Au) +#define CommandStandbyTimeToEmpty (0x1Cu) +#define CommandMaxLoadCurrent (0x1Eu) +#define CommandMaxLoadTimeToEmpty (0x20u) +#define CommandRawCoulombCount (0x22u) +#define CommandAveragePower (0x24u) +#define CommandInternalTemperature (0x28u) +#define CommandCycleCount (0x2Au) +#define CommandStateOfCharge (0x2Cu) +#define CommandStateOfHealth (0x2Eu) +#define CommandChargeVoltage (0x30u) +#define CommandChargeCurrent (0x32u) +#define CommandBTPDischargeSet (0x34u) +#define CommandBTPChargeSet (0x36u) +#define CommandOperationStatus (0x3Au) +#define CommandDesignCapacity (0x3Cu) +#define CommandSelectSubclass (0x3Eu) +#define CommandMACData (0x40u) +#define CommandMACDataSum (0x60u) +#define CommandMACDataLen (0x61u) +#define CommandAnalogCount (0x79u) +#define CommandRawCurrent (0x7Au) +#define CommandRawVoltage (0x7Cu) +#define CommandRawIntTemp (0x7Eu) -#define Control_CONTROL_STATUS 0x0000 -#define Control_DEVICE_NUMBER 0x0001 -#define Control_FW_VERSION 0x0002 -#define Control_BOARD_OFFSET 0x0009 -#define Control_CC_OFFSET 0x000A -#define Control_CC_OFFSET_SAVE 0x000B -#define Control_OCV_CMD 0x000C -#define Control_BAT_INSERT 0x000D -#define Control_BAT_REMOVE 0x000E -#define Control_SET_SNOOZE 0x0013 -#define Control_CLEAR_SNOOZE 0x0014 -#define Control_SET_PROFILE_1 0x0015 -#define Control_SET_PROFILE_2 0x0016 -#define Control_SET_PROFILE_3 0x0017 -#define Control_SET_PROFILE_4 0x0018 -#define Control_SET_PROFILE_5 0x0019 -#define Control_SET_PROFILE_6 0x001A -#define Control_CAL_TOGGLE 0x002D -#define Control_SEALED 0x0030 -#define Control_RESET 0x0041 -#define Control_EXIT_CAL 0x0080 -#define Control_ENTER_CAL 0x0081 -#define Control_ENTER_CFG_UPDATE 0x0090 -#define Control_EXIT_CFG_UPDATE_REINIT 0x0091 -#define Control_EXIT_CFG_UPDATE 0x0092 -#define Control_RETURN_TO_ROM 0x0F00 +#define Control_CONTROL_STATUS (0x0000u) +#define Control_DEVICE_NUMBER (0x0001u) +#define Control_FW_VERSION (0x0002u) +#define Control_HW_VERSION (0x0003u) +#define Control_BOARD_OFFSET (0x0009u) +#define Control_CC_OFFSET (0x000Au) +#define Control_CC_OFFSET_SAVE (0x000Bu) +#define Control_OCV_CMD (0x000Cu) +#define Control_BAT_INSERT (0x000Du) +#define Control_BAT_REMOVE (0x000Eu) +#define Control_SET_SNOOZE (0x0013u) +#define Control_CLEAR_SNOOZE (0x0014u) +#define Control_SET_PROFILE_1 (0x0015u) +#define Control_SET_PROFILE_2 (0x0016u) +#define Control_SET_PROFILE_3 (0x0017u) +#define Control_SET_PROFILE_4 (0x0018u) +#define Control_SET_PROFILE_5 (0x0019u) +#define Control_SET_PROFILE_6 (0x001Au) +#define Control_CAL_TOGGLE (0x002Du) +#define Control_SEALED (0x0030u) +#define Control_RESET (0x0041u) +#define Control_OERATION_STATUS (0x0054u) +#define Control_GAUGING_STATUS (0x0056u) +#define Control_EXIT_CAL (0x0080u) +#define Control_ENTER_CAL (0x0081u) +#define Control_ENTER_CFG_UPDATE (0x0090u) +#define Control_EXIT_CFG_UPDATE_REINIT (0x0091u) +#define Control_EXIT_CFG_UPDATE (0x0092u) +#define Control_RETURN_TO_ROM (0x0F00u) + +#define UnsealKey1 (0x0414u) +#define UnsealKey2 (0x3672u) + +#define FullAccessKey (0xffffu) diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 1a55278dc..6097a8dc9 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -47,7 +47,7 @@ def generate(env): PVSOPTIONS=[ "@.pvsoptions", "-j${PVSNCORES}", - "--disableLicenseExpirationCheck", + # "--disableLicenseExpirationCheck", # "--incremental", # kinda broken on PVS side ], PVSCONVOPTIONS=[ diff --git a/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c index ccbc521a6..37c6a8b1b 100644 --- a/targets/f7/furi_hal/furi_hal_power.c +++ b/targets/f7/furi_hal/furi_hal_power.c @@ -73,18 +73,14 @@ void furi_hal_power_init(void) { // Find and init gauge size_t retry = 2; while(retry > 0) { - furi_hal_power.gauge_ok = bq27220_init(&furi_hal_i2c_handle_power); - if(furi_hal_power.gauge_ok) { - furi_hal_power.gauge_ok = bq27220_apply_data_memory( - &furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); - } + furi_hal_power.gauge_ok = + bq27220_init(&furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); if(furi_hal_power.gauge_ok) { break; } else { - // Normal startup time is 250ms - // But if we try to access gauge at that stage it will become unresponsive - // 2 seconds timeout needed to restart communication - furi_delay_us(2020202); + // Gauge need some time to think about it's behavior + // We must wait, otherwise next init cycle will fail at unseal stage + furi_delay_us(4000000); } retry--; } @@ -110,8 +106,8 @@ void furi_hal_power_init(void) { bool furi_hal_power_gauge_is_ok(void) { bool ret = true; - BatteryStatus battery_status; - OperationStatus operation_status; + Bq27220BatteryStatus battery_status; + Bq27220OperationStatus operation_status; furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); @@ -132,7 +128,7 @@ bool furi_hal_power_gauge_is_ok(void) { bool furi_hal_power_is_shutdown_requested(void) { bool ret = false; - BatteryStatus battery_status; + Bq27220BatteryStatus battery_status; furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); @@ -593,8 +589,8 @@ void furi_hal_power_debug_get(PropertyValueCallback out, void* context) { PropertyValueContext property_context = { .key = key, .value = value, .out = out, .sep = '.', .last = false, .context = context}; - BatteryStatus battery_status; - OperationStatus operation_status; + Bq27220BatteryStatus battery_status; + Bq27220OperationStatus operation_status; furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);