#include "../uk_mbirth_sonicare.h" #include "../sonicare_password.h" #include #include #include #include #include #include #include #include void format_bytes(FuriString* str, const uint8_t* data, size_t size) { for(size_t i = 0; i < size; i++) { furi_string_cat_printf(str, " %02X", data[i]); } } void format_bytes_reverse(FuriString* str, const uint8_t* data, size_t size) { for(size_t i = size; i > 0; i--) { furi_string_cat_printf(str, " %02X", data[i-1]); } } void sonicare_scene_read_complete_widget_callback(GuiButtonType result, InputType type, void* context) { furi_assert(context); Sonicare* app = context; if (type == InputTypeShort) { view_dispatcher_send_custom_event(app->view_dispatcher, result); } } void sonicare_scene_read_complete_on_enter(void* context) { Sonicare* app = context; Widget* widget = app->widget; widget_reset(widget); FURI_LOG_D("sonicare_scene_read_complete", "Pulling Mifare Ultralight data from NFC device"); const MfUltralightData* ul_data = app->nfc_data; FURI_LOG_D("sonicare_scene_read_complete", "Alloc'ing temp_str for output"); FuriString* temp_str = furi_string_alloc(); // Brush type and colour const uint8_t brush_id = ul_data->page[0x1f].data[2]; const char* brush_names[] = { "Unknown brush: 0x00", "Prem. Plaque Def., Wt", "Prem. Plaque Defence, B", "Premium Gum Care, Wt", "Premium Gum Care, Bk", "Premium White, White", "Premium White, Black", "Opt. Plaque Defence, Wt", "Optimal Gum Care, Wt", // 0x08 "Optimal White, White", "Optimal White, Black", "Opt. White (small), Wt", "InterCare, White", "InterCare (small), Wt", "TongueCare+, White", "TongueCare+, Black", "A3 Prem. All-in-One, Wt", // 0x10 "A3 Prem. All-in-One, Bk", "SimplyClean, White", "ProResults, White", "Sensitive, White", "Sensitive, Black", "Gentle Clean, White" }; const int brush_names_max = sizeof(brush_names)/sizeof(brush_names[0]); /* DEBUG STRING LENGTHS for (int i=0; ipage[0x24].data[1]*256 + ul_data->page[0x24].data[0]; uint16_t brushes = seconds / 120; // one brush = 2 minutes if (seconds % 120 > 0) { // The official SoniCare app shows "remaining brushes" and if there's not at least // 2 minutes remaining, it doesn't count as a brush. So, let's include // unfinished brushes in the count. brushes++; } const uint8_t hours = seconds / 3600; seconds %= 3600; const uint8_t minutes = seconds / 60; seconds %= 60; furi_string_cat_printf(temp_str, "Usage: %u brushes (%u:%02u:%02u)\n", (int)brushes, (int)hours, (int)minutes, (int)seconds); // Max Usage uint16_t max_seconds = ul_data->page[0x21].data[1]*256 + ul_data->page[0x21].data[0]; const uint16_t max_brushes = max_seconds / 120; const uint8_t max_hours = max_seconds / 3600; max_seconds %= 3600; const uint8_t max_minutes = max_seconds / 60; max_seconds %= 60; furi_string_cat_printf(temp_str, "Max: %u brushes (%u:%02u:%02u)\n", (int)max_brushes, (int)max_hours, (int)max_minutes, (int)max_seconds); // TODO: Maybe show "replace soon" marker if 170 brushes or more? // TODO: Maybe show "replace head" marker if 180 brushes or more? // Brush Mode const char* brush_modes[] = { "Clean", "White+", "Gum Health", "Deep Clean+", "Sensitive" }; const uint8_t brush_mode = ul_data->page[0x24].data[3]; if (brush_mode<(sizeof(brush_modes)/sizeof(brush_modes[0]))) { furi_string_cat_printf(temp_str, "Last mode: %s\n", brush_modes[brush_mode]); } else { furi_string_cat_printf(temp_str, "Unknown last mode: 0x%02x", brush_mode); } // Brush Intensity const char* brush_intensities[] = { "Low", "Med", "High" }; const uint8_t brush_intensity = ul_data->page[0x24].data[2]; if (brush_intensity<(sizeof(brush_intensities)/sizeof(brush_intensities[0]))) { furi_string_cat_printf(temp_str, "Last intensity: %s\n", brush_intensities[brush_intensity]); } else { furi_string_cat_printf(temp_str, "Unknown last intensity: 0x%02x", brush_intensity); } // Last brush timestamp uint32_t unixtime = ul_data->page[0x26].data[3] * 16777216 + ul_data->page[0x26].data[2] * 65536 + ul_data->page[0x26].data[1] * 256 + ul_data->page[0x26].data[0]; furi_string_cat_printf(temp_str, "Last brush: %li\n", unixtime); if (unixtime > 0) { DateTime brush_dt; datetime_timestamp_to_datetime(unixtime, &brush_dt); furi_string_cat_printf( temp_str, "%04i-%02i-%02i %02i:%02i:%02i UTC\n", brush_dt.year, brush_dt.month, brush_dt.day, brush_dt.hour, brush_dt.minute, brush_dt.second ); } furi_string_cat_str(temp_str, "____________________\n"); // UID furi_string_cat_str(temp_str, "UID:"); format_bytes(temp_str, ul_data->iso14443_3a_data->uid, ul_data->iso14443_3a_data->uid_len); furi_string_cat_str(temp_str, "\n"); // Cache UID + MFG for the reset scene's password derivation. // MFG (10 bytes, e.g. "221214 12K") = page[0x21].data[2..3] + // page[0x22].data[0..3] + page[0x23].data[0..3] // (first 2 bytes of page 0x21 are the max-usage value, not MFG). memcpy(app->sonicare_uid, ul_data->iso14443_3a_data->uid, 7); memcpy(app->sonicare_mfg + 0, ul_data->page[0x21].data + 2, 2); memcpy(app->sonicare_mfg + 2, ul_data->page[0x22].data, 4); memcpy(app->sonicare_mfg + 6, ul_data->page[0x23].data, 4); // Manufacturing Code furi_string_cat_str(temp_str, "MFG: "); FuriString* serial_no = furi_string_alloc(); furi_string_cat_str(serial_no, (char*)(ul_data->page[0x21].data)); furi_string_right(serial_no, 2); furi_string_cat(temp_str, serial_no); furi_string_cat_printf(temp_str, "\n"); // NFC password uint32_t unlock_pwd_big = get_sonicare_password((uint8_t*)ul_data->iso14443_3a_data->uid, (uint8_t*)furi_string_get_cstr(serial_no)); FURI_LOG_D("sonicare_scene_read_complete", "NFC unlock password: 0x%08lx", unlock_pwd_big); uint8_t unlock_pwd[4]; memcpy(unlock_pwd, &unlock_pwd_big, sizeof(unlock_pwd_big)); furi_string_cat_printf(temp_str, "NFC unlock:"); format_bytes_reverse(temp_str, unlock_pwd, 4); furi_string_cat_printf(temp_str, "\n"); // Output to widget widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); furi_string_free(temp_str); furi_string_free(serial_no); widget_add_button_element( widget, GuiButtonTypeRight, "Reset", sonicare_scene_read_complete_widget_callback, app); view_dispatcher_switch_to_view(app->view_dispatcher, SonicareViewWidget); } bool sonicare_scene_read_complete_on_event(void* context, SceneManagerEvent event) { Sonicare* app = context; bool consumed = false; if (event.type == SceneManagerEventTypeCustom) { if (event.event == GuiButtonTypeRight) { scene_manager_next_scene(app->scene_manager, SonicareSceneResetConfirm); consumed = true; } } else if (event.type == SceneManagerEventTypeBack) { // Back button pressed //consumed = true; } return consumed; } void sonicare_scene_read_complete_on_exit(void* context) { Sonicare* app = context; widget_reset(app->widget); }