182 lines
6.5 KiB
C
182 lines
6.5 KiB
C
#include "../uk_mbirth_sonicare.h"
|
|
#include "../sonicare_password.h"
|
|
#include <gui/canvas.h>
|
|
#include <gui/modules/widget.h>
|
|
#include <gui/modules/widget_elements/widget_element.h>
|
|
#include <gui/scene_manager.h>
|
|
#include <gui/view_dispatcher.h>
|
|
#include <uk_mbirth_sonicare_icons.h>
|
|
#include <dolphin/dolphin.h>
|
|
|
|
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; i<brush_names_max; i++) {
|
|
furi_string_cat_printf(temp_str, "\e#%s\n", brush_names[i]);
|
|
}
|
|
*/
|
|
|
|
if (brush_id < brush_names_max) {
|
|
furi_string_cat_printf(temp_str, "\e#%s\n", brush_names[brush_id]);
|
|
} else {
|
|
furi_string_cat_printf(temp_str, "\e#Unknown brush: 0x%02x\n", brush_id);
|
|
}
|
|
|
|
// Usage
|
|
uint16_t seconds = ul_data->page[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?
|
|
|
|
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);
|
|
}
|