8 Commits
Author SHA1 Message Date
mbirth b7ed4ca160 Output time of last brush
FAP: Build for multiple SDK sources / ufbt: Build for dev channel (push) Successful in 2m56s
FAP: Build for multiple SDK sources / ufbt: Build for release channel (push) Successful in 3m6s
Signed-off-by: Markus Birth <markus@birth-online.de>
2026-08-15 01:51:15 +01:00
mbirth cd68cf9f8f Show last intensity setting
FAP: Build for multiple SDK sources / ufbt: Build for dev channel (push) Successful in 2m58s
FAP: Build for multiple SDK sources / ufbt: Build for release channel (push) Successful in 3m1s
Signed-off-by: Markus Birth <markus@birth-online.de>
2026-08-15 01:29:17 +01:00
mbirth 0ce5c46659 Show last brush mode
Signed-off-by: Markus Birth <markus@birth-online.de>
2026-08-15 01:25:34 +01:00
mbirth 9d1faa9a23 Make var const
Signed-off-by: Markus Birth <markus@birth-online.de>
2026-08-15 01:25:28 +01:00
mbirth b7fc06ce5b Fix string lengths to fit one line
Signed-off-by: Markus Birth <markus@birth-online.de>
2026-08-15 01:21:12 +01:00
mbirth 58edc59ae6 Move UID and MFG below usage data and separate usage from internals
FAP: Build for multiple SDK sources / ufbt: Build for dev channel (push) Successful in 3m5s
FAP: Build for multiple SDK sources / ufbt: Build for release channel (push) Successful in 2m55s
Signed-off-by: Markus Birth <markus@birth-online.de>
2026-08-15 01:16:00 +01:00
mbirth 8cd7da7949 Show brush type instead of "NTAG213" header
FAP: Build for multiple SDK sources / ufbt: Build for dev channel (push) Successful in 3m15s
FAP: Build for multiple SDK sources / ufbt: Build for release channel (push) Successful in 3m12s
Signed-off-by: Markus Birth <markus@birth-online.de>
2026-08-15 01:05:07 +01:00
mbirth 4a65c603c8 Copy mode and intensity settings before reset
Signed-off-by: Markus Birth <markus@birth-online.de>
2026-08-15 00:44:05 +01:00
2 changed files with 122 additions and 23 deletions
+116 -23
View File
@@ -5,6 +5,7 @@
#include <gui/modules/widget_elements/widget_element.h> #include <gui/modules/widget_elements/widget_element.h>
#include <gui/scene_manager.h> #include <gui/scene_manager.h>
#include <gui/view_dispatcher.h> #include <gui/view_dispatcher.h>
#include <datetime/datetime.h>
#include <uk_mbirth_sonicare_icons.h> #include <uk_mbirth_sonicare_icons.h>
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
@@ -34,36 +35,53 @@ void sonicare_scene_read_complete_on_enter(void* context) {
widget_reset(widget); widget_reset(widget);
const NfcDevice* nfc_device = app->nfc_device;
FURI_LOG_D("sonicare_scene_read_complete", "Pulling Mifare Ultralight data from NFC device"); FURI_LOG_D("sonicare_scene_read_complete", "Pulling Mifare Ultralight data from NFC device");
const MfUltralightData* ul_data = app->nfc_data; const MfUltralightData* ul_data = app->nfc_data;
FURI_LOG_D("sonicare_scene_read_complete", "Alloc'ing temp_str for output"); FURI_LOG_D("sonicare_scene_read_complete", "Alloc'ing temp_str for output");
FuriString* temp_str = furi_string_alloc(); FuriString* temp_str = furi_string_alloc();
furi_string_cat_printf(temp_str, "\e#%s\n", nfc_device_get_name(nfc_device, NfcDeviceNameTypeFull));
// UID // Brush type and colour
furi_string_cat_str(temp_str, "UID:"); const uint8_t brush_id = ul_data->page[0x1f].data[2];
format_bytes(temp_str, ul_data->iso14443_3a_data->uid, ul_data->iso14443_3a_data->uid_len); const char* brush_names[] = {
furi_string_cat_str(temp_str, "\n"); "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"
};
// Cache UID + MFG for the reset scene's password derivation. const int brush_names_max = sizeof(brush_names)/sizeof(brush_names[0]);
// MFG (10 bytes, e.g. "221214 12K") = page[0x21].data[2..3] +
// page[0x22].data[0..3] + page[0x23].data[0..3] /* DEBUG STRING LENGTHS
// (first 2 bytes of page 0x21 are the max-usage value, not MFG). for (int i=0; i<brush_names_max; i++) {
memcpy(app->sonicare_uid, ul_data->iso14443_3a_data->uid, 7); furi_string_cat_printf(temp_str, "\e#%s\n", brush_names[i]);
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);
if (brush_id < brush_names_max) {
// Manufacturing Code furi_string_cat_printf(temp_str, "\e#%s\n", brush_names[brush_id]);
furi_string_cat_str(temp_str, "MFG: "); } else {
FuriString* serial_no = furi_string_alloc(); furi_string_cat_printf(temp_str, "\e#Unknown brush: 0x%02x\n", brush_id);
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");
// Usage // Usage
uint16_t seconds = ul_data->page[0x24].data[1]*256 + ul_data->page[0x24].data[0]; uint16_t seconds = ul_data->page[0x24].data[1]*256 + ul_data->page[0x24].data[0];
@@ -92,6 +110,81 @@ void sonicare_scene_read_complete_on_enter(void* context) {
// TODO: Maybe show "replace soon" marker if 170 brushes or more? // TODO: Maybe show "replace soon" marker if 170 brushes or more?
// TODO: Maybe show "replace head" marker if 180 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 // 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)); 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); FURI_LOG_D("sonicare_scene_read_complete", "NFC unlock password: 0x%08lx", unlock_pwd_big);
+6
View File
@@ -39,10 +39,16 @@ static NfcCommand sonicare_scene_reset_poller_callback(NfcGenericEvent event, vo
} else if(ev->type == MfUltralightPollerEventTypeAuthSuccess) { } else if(ev->type == MfUltralightPollerEventTypeAuthSuccess) {
FURI_LOG_I("sonicare_scene_reset", "Auth success, writing page 0x24"); FURI_LOG_I("sonicare_scene_reset", "Auth success, writing page 0x24");
const MfUltralightData* ul_data = app->nfc_data;
MfUltralightPoller* poller = (MfUltralightPoller*)event.instance; MfUltralightPoller* poller = (MfUltralightPoller*)event.instance;
// Official reset: write page 0x24 = {00 00 02 00} // Official reset: write page 0x24 = {00 00 02 00}
MfUltralightPage page = {.data = {0x00, 0x00, 0x02, 0x00}}; MfUltralightPage page = {.data = {0x00, 0x00, 0x02, 0x00}};
// copy mode and intensity settings
page.data[2] = ul_data->page[SONICARE_PAGE_USAGE].data[2];
page.data[3] = ul_data->page[SONICARE_PAGE_USAGE].data[3];
MfUltralightError err = MfUltralightError err =
mf_ultralight_poller_write_page(poller, SONICARE_PAGE_USAGE, &page); mf_ultralight_poller_write_page(poller, SONICARE_PAGE_USAGE, &page);