diff --git a/applications/dolphin/dolphin.c b/applications/dolphin/dolphin.c index 8edcd826..8506c12a 100644 --- a/applications/dolphin/dolphin.c +++ b/applications/dolphin/dolphin.c @@ -1,100 +1,126 @@ #include "dolphin_i.h" -void dolphin_draw_callback(Canvas* canvas, void* context) { +bool dolphin_view_first_start_input(InputEvent* event, void* context) { + furi_assert(event); + furi_assert(context); Dolphin* dolphin = context; - - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - if(dolphin->screen == DolphinScreenIdle) { - dolphin_draw_idle(canvas, dolphin); - } else if(dolphin->screen == DolphinScreenDebug) { - dolphin_draw_debug(canvas, dolphin); - } else if(dolphin->screen == DolphinScreenStats) { - dolphin_draw_stats(canvas, dolphin); + if(event->state) { + if(event->input == InputRight) { + uint32_t page; + with_view_model( + dolphin->idle_view_first_start, + (DolphinViewFirstStartModel * model) { page = ++model->page; }); + if(page > 8) { + dolphin_save(dolphin); + view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain); + } + } } + // All events consumed + return true; } -void dolphin_draw_idle(Canvas* canvas, Dolphin* dolphin) { - canvas_draw_icon(canvas, 128 - 80, 0, dolphin->icon); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 2, 10, "/\\: Stats"); - canvas_draw_str(canvas, 5, 32, "OK: Menu"); - canvas_draw_str(canvas, 2, 52, "\\/: Version"); -} - -void dolphin_draw_debug(Canvas* canvas, Dolphin* dolphin) { - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 2, 10, "Version info:"); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 5, 22, TARGET " " BUILD_DATE); - canvas_draw_str(canvas, 5, 32, GIT_BRANCH); - canvas_draw_str(canvas, 5, 42, GIT_BRANCH_NUM); - canvas_draw_str(canvas, 5, 52, GIT_COMMIT); -} - -void dolphin_draw_stats(Canvas* canvas, Dolphin* dolphin) { - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 2, 10, "Dolphin stats:"); - - char buffer[64]; - canvas_set_font(canvas, FontSecondary); - snprintf(buffer, 64, "Icounter: %ld", dolphin_state_get_icounter(dolphin->state)); - canvas_draw_str(canvas, 5, 22, buffer); - snprintf(buffer, 64, "Butthurt: %ld", dolphin_state_get_butthurt(dolphin->state)); - canvas_draw_str(canvas, 5, 32, buffer); - canvas_draw_str(canvas, 5, 40, "< > change icounter"); -} - -void dolphin_input_callback(InputEvent* event, void* context) { +bool dolphin_view_idle_main_input(InputEvent* event, void* context) { + furi_assert(event); + furi_assert(context); Dolphin* dolphin = context; - if(!event->state) return; + if(event->state) { + if(event->input == InputOk) { + with_value_mutex( + dolphin->menu_vm, (Menu * menu) { menu_ok(menu); }); + } else if(event->input == InputUp) { + view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleStats); + } else if(event->input == InputDown) { + view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleDebug); + } + } + // All events consumed + return true; +} - if(event->input == InputOk) { - with_value_mutex( - dolphin->menu_vm, (Menu * menu) { menu_ok(menu); }); - } else if(event->input == InputUp) { - if(dolphin->screen != DolphinScreenStats) { - dolphin->screen++; - } - } else if(event->input == InputDown) { - if(dolphin->screen != DolphinScreenDebug) { - dolphin->screen--; - } - } else if(event->input == InputBack) { - dolphin->screen = DolphinScreenIdle; - } else if(event->input == InputLeft) { - dolphin_deed(dolphin, DolphinDeedIButtonEmulate); - } else if(event->input == InputRight) { +bool dolphin_view_idle_stats_input(InputEvent* event, void* context) { + furi_assert(event); + furi_assert(context); + Dolphin* dolphin = context; + + if(!event->state) return false; + + if(event->input == InputLeft) { dolphin_deed(dolphin, DolphinDeedWrong); + } else if(event->input == InputRight) { + dolphin_deed(dolphin, DolphinDeedIButtonRead); + } else if(event->input == InputOk) { + dolphin_save(dolphin); + } else { + return false; } - widget_update(dolphin->widget); + return true; } Dolphin* dolphin_alloc() { Dolphin* dolphin = furi_alloc(sizeof(Dolphin)); - - dolphin->icon = assets_icons_get(I_Flipper_young_80x60); - icon_start_animation(dolphin->icon); - - dolphin->widget = widget_alloc(); - widget_draw_callback_set(dolphin->widget, dolphin_draw_callback, dolphin); - widget_input_callback_set(dolphin->widget, dolphin_input_callback, dolphin); - - dolphin->menu_vm = furi_open("menu"); - furi_check(dolphin->menu_vm); - - dolphin->state = dolphin_state_alloc(); - - dolphin->screen = DolphinScreenIdle; - + // Message queue dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL); furi_check(dolphin->event_queue); + // State + dolphin->state = dolphin_state_alloc(); + // Menu + dolphin->menu_vm = furi_open("menu"); + furi_check(dolphin->menu_vm); + // GUI + dolphin->idle_view_dispatcher = view_dispatcher_alloc(); + // First start View + dolphin->idle_view_first_start = view_alloc(); + view_allocate_model( + dolphin->idle_view_first_start, ViewModelTypeLockFree, sizeof(DolphinViewFirstStartModel)); + view_set_context(dolphin->idle_view_first_start, dolphin); + view_set_draw_callback(dolphin->idle_view_first_start, dolphin_view_first_start_draw); + view_set_input_callback(dolphin->idle_view_first_start, dolphin_view_first_start_input); + view_dispatcher_add_view( + dolphin->idle_view_dispatcher, DolphinViewFirstStart, dolphin->idle_view_first_start); + // Main Idle View + dolphin->idle_view_main = view_alloc(); + view_set_context(dolphin->idle_view_main, dolphin); + view_set_draw_callback(dolphin->idle_view_main, dolphin_view_idle_main_draw); + view_set_input_callback(dolphin->idle_view_main, dolphin_view_idle_main_input); + view_dispatcher_add_view( + dolphin->idle_view_dispatcher, DolphinViewIdleMain, dolphin->idle_view_main); + // Stats Idle View + dolphin->idle_view_stats = view_alloc(); + view_set_context(dolphin->idle_view_stats, dolphin); + view_allocate_model( + dolphin->idle_view_stats, ViewModelTypeLockFree, sizeof(DolphinViewIdleStatsModel)); + with_view_model( + dolphin->idle_view_stats, (DolphinViewIdleStatsModel * model) { + model->icounter = dolphin_state_get_icounter(dolphin->state); + model->butthurt = dolphin_state_get_butthurt(dolphin->state); + }); + view_set_draw_callback(dolphin->idle_view_stats, dolphin_view_idle_stats_draw); + view_set_input_callback(dolphin->idle_view_stats, dolphin_view_idle_stats_input); + view_set_previous_callback(dolphin->idle_view_stats, dolphin_view_idle_back); + view_dispatcher_add_view( + dolphin->idle_view_dispatcher, DolphinViewIdleStats, dolphin->idle_view_stats); + // Debug Idle View + dolphin->idle_view_debug = view_alloc(); + view_set_draw_callback(dolphin->idle_view_debug, dolphin_view_idle_debug_draw); + view_set_previous_callback(dolphin->idle_view_debug, dolphin_view_idle_back); + view_dispatcher_add_view( + dolphin->idle_view_dispatcher, DolphinViewIdleDebug, dolphin->idle_view_debug); + return dolphin; } +void dolphin_save(Dolphin* dolphin) { + furi_assert(dolphin); + DolphinEvent event; + event.type = DolphinEventTypeSave; + furi_check(osMessageQueuePut(dolphin->event_queue, &event, 0, osWaitForever) == osOK); +} + void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) { + furi_assert(dolphin); DolphinEvent event; event.type = DolphinEventTypeDeed; event.deed = deed; @@ -105,7 +131,12 @@ void dolphin_task() { Dolphin* dolphin = dolphin_alloc(); Gui* gui = furi_open("gui"); - gui_add_widget(gui, dolphin->widget, GuiLayerNone); + view_dispatcher_attach_to_gui(dolphin->idle_view_dispatcher, gui, ViewDispatcherTypeWindow); + if(dolphin_state_load(dolphin->state)) { + view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain); + } else { + view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewFirstStart); + } if(!furi_create("dolphin", dolphin)) { printf("[dolphin_task] cannot create the dolphin record\n"); @@ -119,6 +150,13 @@ void dolphin_task() { furi_check(osMessageQueueGet(dolphin->event_queue, &event, NULL, osWaitForever) == osOK); if(event.type == DolphinEventTypeDeed) { dolphin_state_on_deed(dolphin->state, event.deed); + with_view_model( + dolphin->idle_view_stats, (DolphinViewIdleStatsModel * model) { + model->icounter = dolphin_state_get_icounter(dolphin->state); + model->butthurt = dolphin_state_get_butthurt(dolphin->state); + }); + } else if(event.type == DolphinEventTypeSave) { + dolphin_state_save(dolphin->state); } } } diff --git a/applications/dolphin/dolphin.h b/applications/dolphin/dolphin.h index 38ca1e90..676f5dc5 100644 --- a/applications/dolphin/dolphin.h +++ b/applications/dolphin/dolphin.h @@ -4,8 +4,8 @@ typedef struct Dolphin Dolphin; -/* - * Deed complete notification. Call it on deed completion. +/* Deed complete notification. Call it on deed completion. * See dolphin_deed.h for available deeds. In futures it will become part of assets. + * Thread safe */ void dolphin_deed(Dolphin* dolphin, DolphinDeed deed); diff --git a/applications/dolphin/dolphin_i.h b/applications/dolphin/dolphin_i.h index ab7e0365..79a2b2fa 100644 --- a/applications/dolphin/dolphin_i.h +++ b/applications/dolphin/dolphin_i.h @@ -2,20 +2,21 @@ #include "dolphin.h" #include "dolphin_state.h" +#include "dolphin_views.h" #include #include -#include +#include #include #include #include - #include typedef enum { DolphinEventTypeDeed, + DolphinEventTypeSave, } DolphinEventType; typedef struct { @@ -25,27 +26,24 @@ typedef struct { }; } DolphinEvent; -typedef enum { - DolphinScreenDebug, - DolphinScreenIdle, - DolphinScreenStats, -} DolphinScreen; - struct Dolphin { - Icon* icon; - Widget* widget; - ValueMutex* menu_vm; - // State - DolphinState* state; - DolphinScreen screen; // Internal message queue osMessageQueueId_t event_queue; + // State + DolphinState* state; + // Menu + ValueMutex* menu_vm; + // GUI + ViewDispatcher* idle_view_dispatcher; + View* idle_view_first_start; + View* idle_view_main; + View* idle_view_stats; + View* idle_view_debug; }; -void dolphin_draw_callback(Canvas* canvas, void* context); -void dolphin_draw_idle(Canvas* canvas, Dolphin* dolphin); -void dolphin_draw_debug(Canvas* canvas, Dolphin* dolphin); -void dolphin_draw_stats(Canvas* canvas, Dolphin* dolphin); -void dolphin_input_callback(InputEvent* event, void* context); - Dolphin* dolphin_alloc(); + +/* Save Dolphin state (write to permanent memory) + * Thread safe + */ +void dolphin_save(Dolphin* dolphin); diff --git a/applications/dolphin/dolphin_state.c b/applications/dolphin/dolphin_state.c index dcb9fddc..afe007bb 100644 --- a/applications/dolphin/dolphin_state.c +++ b/applications/dolphin/dolphin_state.c @@ -1,18 +1,35 @@ #include "dolphin_state.h" +#include #include typedef struct { - uint32_t ibutton; - uint32_t nfc; - uint32_t ir; - uint32_t rfid; -} DolphinLimit; + uint8_t magic; + uint8_t version; + uint8_t checksum; + uint8_t flags; + uint32_t timestamp; +} DolphinDataHeader; -struct DolphinState { +#define DOLPHIN_DATA_PAGE 0xC0 +#define DOLPHIN_DATA_HEADER_ADDRESS 0x080C0000U +#define DOLPHIN_DATA_DATA_ADDRESS (DOLPHIN_DATA_HEADER_ADDRESS + sizeof(DolphinDataHeader)) + +#define DOLPHIN_DATA_HEADER_MAGIC 0xD0 +#define DOLPHIN_DATA_HEADER_VERSION 0x00 + +typedef struct { + uint32_t limit_ibutton; + uint32_t limit_nfc; + uint32_t limit_ir; + uint32_t limit_rfid; + + uint32_t flags; uint32_t icounter; uint32_t butthurt; +} DolphinData; - DolphinLimit limit; +struct DolphinState { + DolphinData data; }; DolphinState* dolphin_state_alloc() { @@ -24,29 +41,81 @@ void dolphin_state_release(DolphinState* dolphin_state) { free(dolphin_state); } -void dolphin_state_save(DolphinState* dolphin_state) { +bool dolphin_state_save(DolphinState* dolphin_state) { + if(!api_hal_flash_erase(DOLPHIN_DATA_PAGE, 1)) { + return false; + } + + uint8_t* source = (uint8_t*)&dolphin_state->data; + uint8_t checksum = 0; + for(size_t i = 0; i < sizeof(DolphinData); i++) { + checksum += source[i]; + } + DolphinDataHeader header; + header.magic = DOLPHIN_DATA_HEADER_MAGIC; + header.version = DOLPHIN_DATA_HEADER_VERSION; + header.checksum = checksum; + header.flags = 0; + header.timestamp = 0; + if(!api_hal_flash_write_dword(DOLPHIN_DATA_HEADER_ADDRESS, *(uint64_t*)&header)) { + return false; + } + + uint8_t destination[sizeof(uint64_t)]; + size_t block_count = sizeof(DolphinData) / sizeof(uint64_t) + 1; + size_t offset = 0; + for(size_t i = 0; i < block_count; i++) { + for(size_t n = 0; n < sizeof(uint64_t); n++) { + if(offset < sizeof(DolphinData)) { + destination[n] = source[offset]; + } else { + destination[n] = 0; + } + offset++; + } + if(!api_hal_flash_write_dword( + DOLPHIN_DATA_DATA_ADDRESS + i * sizeof(uint64_t), *(uint64_t*)destination)) { + return false; + } + } + return true; } -void dolphin_state_load(DolphinState* dolphin_state) { +bool dolphin_state_load(DolphinState* dolphin_state) { + const DolphinDataHeader* header = (const DolphinDataHeader*)DOLPHIN_DATA_HEADER_ADDRESS; + if(header->magic == DOLPHIN_DATA_HEADER_MAGIC && + header->version == DOLPHIN_DATA_HEADER_VERSION) { + uint8_t checksum = 0; + const uint8_t* source = (const uint8_t*)DOLPHIN_DATA_DATA_ADDRESS; + for(size_t i = 0; i < sizeof(DolphinData); i++) { + checksum += source[i]; + } + if(header->checksum == checksum) { + memcpy( + &dolphin_state->data, (const void*)DOLPHIN_DATA_DATA_ADDRESS, sizeof(DolphinData)); + return true; + } + } + return false; } void dolphin_state_clear(DolphinState* dolphin_state) { - memset(dolphin_state, 0, sizeof(DolphinState)); + memset(&dolphin_state->data, 0, sizeof(DolphinData)); } void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) { const DolphinDeedWeight* deed_weight = dolphin_deed_weight(deed); - int32_t icounter = dolphin_state->icounter + deed_weight->icounter; + int32_t icounter = dolphin_state->data.icounter + deed_weight->icounter; if(icounter >= 0) { - dolphin_state->icounter = icounter; + dolphin_state->data.icounter = icounter; } } uint32_t dolphin_state_get_icounter(DolphinState* dolphin_state) { - return dolphin_state->icounter; + return dolphin_state->data.icounter; } uint32_t dolphin_state_get_butthurt(DolphinState* dolphin_state) { - return dolphin_state->butthurt; + return dolphin_state->data.butthurt; } diff --git a/applications/dolphin/dolphin_state.h b/applications/dolphin/dolphin_state.h index c3320b84..33433b2b 100644 --- a/applications/dolphin/dolphin_state.h +++ b/applications/dolphin/dolphin_state.h @@ -1,6 +1,7 @@ #pragma once #include "dolphin_deed.h" +#include #include typedef struct DolphinState DolphinState; @@ -9,9 +10,9 @@ DolphinState* dolphin_state_alloc(); void dolphin_state_release(DolphinState* dolphin_state); -void dolphin_state_save(DolphinState* dolphin_state); +bool dolphin_state_save(DolphinState* dolphin_state); -void dolphin_state_load(DolphinState* dolphin_state); +bool dolphin_state_load(DolphinState* dolphin_state); void dolphin_state_clear(DolphinState* dolphin_state); diff --git a/applications/dolphin/dolphin_views.c b/applications/dolphin/dolphin_views.c new file mode 100644 index 00000000..55f67075 --- /dev/null +++ b/applications/dolphin/dolphin_views.c @@ -0,0 +1,69 @@ +#include "dolphin_views.h" +#include + +void dolphin_view_first_start_draw(Canvas* canvas, void* model) { + DolphinViewFirstStartModel* m = model; + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + if(m->page == 0) { + canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart0_128x54); + } else if(m->page == 1) { + canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart1_128x54); + } else if(m->page == 2) { + canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart2_128x54); + } else if(m->page == 3) { + canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart3_128x54); + } else if(m->page == 4) { + canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart4_128x54); + } else if(m->page == 5) { + canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart5_128x54); + } else if(m->page == 6) { + canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart6_128x54); + } else if(m->page == 7) { + canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart7_128x54); + } else if(m->page == 8) { + canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart8_128x54); + } +} + +void dolphin_view_idle_main_draw(Canvas* canvas, void* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon_name(canvas, 128 - 80, 0, I_Flipper_young_80x60); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 10, "/\\: Stats"); + canvas_draw_str(canvas, 5, 32, "OK: Menu"); + canvas_draw_str(canvas, 2, 52, "\\/: Version"); +} + +void dolphin_view_idle_stats_draw(Canvas* canvas, void* model) { + DolphinViewIdleStatsModel* m = model; + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 2, 10, "Dolphin stats:"); + + char buffer[64]; + canvas_set_font(canvas, FontSecondary); + snprintf(buffer, 64, "Icounter: %ld", m->icounter); + canvas_draw_str(canvas, 5, 22, buffer); + snprintf(buffer, 64, "Butthurt: %ld", m->butthurt); + canvas_draw_str(canvas, 5, 32, buffer); + canvas_draw_str(canvas, 5, 40, "< > change icounter"); +} + +void dolphin_view_idle_debug_draw(Canvas* canvas, void* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 2, 10, "Version info:"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 5, 22, TARGET " " BUILD_DATE); + canvas_draw_str(canvas, 5, 32, GIT_BRANCH); + canvas_draw_str(canvas, 5, 42, GIT_BRANCH_NUM); + canvas_draw_str(canvas, 5, 52, GIT_COMMIT); +} + +uint32_t dolphin_view_idle_back(void* context) { + return DolphinViewIdleMain; +} diff --git a/applications/dolphin/dolphin_views.h b/applications/dolphin/dolphin_views.h new file mode 100644 index 00000000..8c353e5d --- /dev/null +++ b/applications/dolphin/dolphin_views.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include + +// Idle scree +typedef enum { + DolphinViewFirstStart, + DolphinViewIdleMain, + DolphinViewIdleStats, + DolphinViewIdleDebug, +} DolphinViewIdle; + +typedef struct { + uint32_t page; +} DolphinViewFirstStartModel; + +void dolphin_view_first_start_draw(Canvas* canvas, void* model); +bool dolphin_view_first_start_input(InputEvent* event, void* context); + +typedef struct { + uint32_t icounter; + uint32_t butthurt; +} DolphinViewIdleStatsModel; + +void dolphin_view_idle_main_draw(Canvas* canvas, void* model); +bool dolphin_view_idle_main_input(InputEvent* event, void* context); +void dolphin_view_idle_stats_draw(Canvas* canvas, void* model); +bool dolphin_view_idle_stats_input(InputEvent* event, void* context); +void dolphin_view_idle_debug_draw(Canvas* canvas, void* model); +uint32_t dolphin_view_idle_back(void* context); diff --git a/applications/gui/canvas.c b/applications/gui/canvas.c index b77a1091..7d4f8261 100644 --- a/applications/gui/canvas.c +++ b/applications/gui/canvas.c @@ -1,5 +1,4 @@ #include "canvas_i.h" -#include "icon.h" #include "icon_i.h" #include @@ -113,6 +112,14 @@ void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, Icon* icon) { &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_get_data(icon)); } +void canvas_draw_icon_name(Canvas* canvas, uint8_t x, uint8_t y, IconName name) { + furi_assert(canvas); + const IconData* data = assets_icons_get_data(name); + x += canvas->offset_x; + y += canvas->offset_y; + u8g2_DrawXBM(&canvas->fb, x, y, data->width, data->height, data->frames[0]); +} + void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y) { furi_assert(canvas); x += canvas->offset_x; diff --git a/applications/gui/canvas.h b/applications/gui/canvas.h index a232b8ce..2ce6b309 100644 --- a/applications/gui/canvas.h +++ b/applications/gui/canvas.h @@ -3,6 +3,7 @@ #include #include #include +#include typedef enum { ColorWhite = 0x00, @@ -46,10 +47,15 @@ void canvas_set_font(Canvas* canvas, Font font); void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str); /* - * Draw icon at position defined by x,y. + * Draw stateful icon at position defined by x,y. */ void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, Icon* icon); +/* + * Draw stateless icon at position defined by x,y. + */ +void canvas_draw_icon_name(Canvas* canvas, uint8_t x, uint8_t y, IconName name); + /* * Draw xbm icon of width, height at position defined by x,y. */ diff --git a/applications/gui/view.c b/applications/gui/view.c new file mode 100644 index 00000000..aa506e74 --- /dev/null +++ b/applications/gui/view.c @@ -0,0 +1,140 @@ +#include "view_i.h" + +View* view_alloc() { + View* view = furi_alloc(sizeof(View)); + return view; +} + +void view_free(View* view) { + furi_assert(view); + view_free_model(view); + free(view); +} + +void view_set_dispatcher(View* view, ViewDispatcher* view_dispatcher) { + furi_assert(view); + furi_assert(view_dispatcher); + furi_assert(view->dispatcher == NULL); + view->dispatcher = view_dispatcher; +} + +void view_set_draw_callback(View* view, ViewDrawCallback callback) { + furi_assert(view); + furi_assert(view->draw_callback == NULL); + view->draw_callback = callback; +} + +void view_set_input_callback(View* view, ViewInputCallback callback) { + furi_assert(view); + furi_assert(view->input_callback == NULL); + view->input_callback = callback; +} + +void view_set_previous_callback(View* view, ViewNavigationCallback callback) { + furi_assert(view); + view->previous_callback = callback; +} + +void view_set_next_callback(View* view, ViewNavigationCallback callback) { + furi_assert(view); + view->next_callback = callback; +} + +void view_set_context(View* view, void* context) { + furi_assert(view); + furi_assert(context); + view->context = context; +} + +void view_allocate_model(View* view, ViewModelType type, size_t size) { + furi_assert(view); + furi_assert(size > 0); + furi_assert(view->model_type == ViewModelTypeNone); + furi_assert(view->model == NULL); + view->model_type = type; + if(view->model_type == ViewModelTypeLockFree) { + view->model = furi_alloc(size); + } else if(view->model_type == ViewModelTypeLocking) { + ViewModelLocking* model = furi_alloc(sizeof(ViewModelLocking)); + model->mutex = osMutexNew(NULL); + furi_check(model->mutex); + model->data = furi_alloc(size); + view->model = model; + } else { + furi_assert(false); + } +} + +void view_free_model(View* view) { + furi_assert(view); + if(view->model_type == ViewModelTypeNone) { + return; + } else if(view->model_type == ViewModelTypeLockFree) { + free(view->model); + } else if(view->model_type == ViewModelTypeLocking) { + ViewModelLocking* model = view->model; + furi_check(osMutexDelete(model->mutex) == osOK); + free(model->data); + free(model); + view->model = NULL; + } else { + furi_assert(false); + } +} + +void* view_get_model(View* view) { + furi_assert(view); + if(view->model_type == ViewModelTypeLocking) { + ViewModelLocking* model = (ViewModelLocking*)(view->model); + furi_check(osMutexAcquire(model->mutex, osWaitForever) == osOK); + return model->data; + } + return view->model; +} + +void view_commit_model(View* view) { + furi_assert(view); + if(view->model_type == ViewModelTypeLocking) { + ViewModelLocking* model = (ViewModelLocking*)(view->model); + furi_check(osMutexRelease(model->mutex) == osOK); + } + if(view->dispatcher) { + view_dispatcher_update(view->dispatcher, view); + } +} + +void view_draw(View* view, Canvas* canvas) { + furi_assert(view); + if(view->draw_callback) { + void* data = view_get_model(view); + view->draw_callback(canvas, data); + view_commit_model(view); + } +} + +bool view_input(View* view, InputEvent* event) { + furi_assert(view); + if(view->input_callback) { + return view->input_callback(event, view->context); + } else { + return false; + } +} + +uint32_t view_previous(View* view) { + furi_assert(view); + if(view->previous_callback) { + return view->previous_callback(view->context); + } else { + return VIEW_IGNORE; + } +} + +uint32_t view_next(View* view) { + furi_assert(view); + if(view->next_callback) { + return view->next_callback(view->context); + } else { + return VIEW_IGNORE; + } +} diff --git a/applications/gui/view.h b/applications/gui/view.h new file mode 100644 index 00000000..e22e0715 --- /dev/null +++ b/applications/gui/view.h @@ -0,0 +1,129 @@ +#pragma once + +#include +#include "canvas.h" + +/* Hides drawing widget */ +#define VIEW_NONE 0xFFFFFFFF +/* Ignore navigation event */ +#define VIEW_IGNORE 0xFFFFFFFE +/* Deatch from gui, deallocate Views and ViewDispatcher + * BE SUPER CAREFUL, deallocation happens automatically on GUI thread + * You ARE NOT owning ViewDispatcher and Views instances + */ +#define VIEW_DESTROY 0xFFFFFFFA + +/* View Draw callback + * @param canvas, pointer to canvas + * @param view_model, pointer to context + * @warning called from GUI thread + */ +typedef void (*ViewDrawCallback)(Canvas* canvas, void* model); + +/* View Input callback + * @param event, pointer to input event data + * @param context, pointer to context + * @return true if event handled, false if event ignored + * @warning called from GUI thread + */ +typedef bool (*ViewInputCallback)(InputEvent* event, void* context); + +/* View navigation callback + * @param context, pointer to context + * @return next view id + * @warning called from GUI thread + */ +typedef uint32_t (*ViewNavigationCallback)(void* context); + +/* View model types */ +typedef enum { + /* Model is not allocated */ + ViewModelTypeNone, + /* Model consist of atomic types and/or partial update is not critical for rendering. + * Lock free. + */ + ViewModelTypeLockFree, + /* Model access is guarded with mutex. + * Locking gui thread. + */ + ViewModelTypeLocking, +} ViewModelType; + +typedef struct View View; + +/* Allocate and init View + * @return pointer to View + */ +View* view_alloc(); + +/* Free View + * @param pointer to View + */ +void view_free(View* view); + +/* Set View Draw callback + * @param view, pointer to View + * @param callback, draw callback + */ +void view_set_draw_callback(View* view, ViewDrawCallback callback); + +/* Set View Draw callback + * @param view, pointer to View + * @param callback, input callback + */ +void view_set_input_callback(View* view, ViewInputCallback callback); + +/* Set Navigation Previous callback + * @param view, pointer to View + * @param callback, input callback + */ +void view_set_previous_callback(View* view, ViewNavigationCallback callback); + +/* Set Navigation Next callback + * @param view, pointer to View + * @param callback, input callback + */ +void view_set_next_callback(View* view, ViewNavigationCallback callback); + +/* Set View Draw callback + * @param view, pointer to View + * @param context, context for callbacks + */ +void view_set_context(View* view, void* context); + +/* Allocate view model. + * @param view, pointer to View + * @param type, View Model Type + * @param size, size + */ +void view_allocate_model(View* view, ViewModelType type, size_t size); + +/* Free view model data memory. + * @param view, pointer to View + */ +void view_free_model(View* view); + +/* Get view model data + * @param view, pointer to View + * @return pointer to model data + * @warning Don't forget to commit model changes + */ +void* view_get_model(View* view); + +/* Commit view model + * @param view, pointer to View + */ +void view_commit_model(View* view); + +/* + * With clause for view model + * @param view, View instance pointer + * @param function_body a (){} lambda declaration, + * executed within you parent function context. + */ +#define with_view_model(view, function_body) \ + { \ + void* p = view_get_model(view); \ + ({ void __fn__ function_body __fn__; })(p); \ + view_commit_model(view); \ + } diff --git a/applications/gui/view_dispatcher.c b/applications/gui/view_dispatcher.c new file mode 100644 index 00000000..a095d17b --- /dev/null +++ b/applications/gui/view_dispatcher.c @@ -0,0 +1,113 @@ +#include "view_dispatcher_i.h" + +ViewDispatcher* view_dispatcher_alloc() { + ViewDispatcher* view_dispatcher = furi_alloc(sizeof(ViewDispatcher)); + + view_dispatcher->widget = widget_alloc(); + widget_draw_callback_set( + view_dispatcher->widget, view_dispatcher_draw_callback, view_dispatcher); + widget_input_callback_set( + view_dispatcher->widget, view_dispatcher_input_callback, view_dispatcher); + widget_enabled_set(view_dispatcher->widget, false); + + ViewDict_init(view_dispatcher->views); + + return view_dispatcher; +} + +void view_dispatcher_free(ViewDispatcher* view_dispatcher) { + // Detach from gui + if(view_dispatcher->gui) { + gui_remove_widget(view_dispatcher->gui, view_dispatcher->widget); + } + // Free views + ViewDict_it_t it; + ViewDict_it(it, view_dispatcher->views); + while(!ViewDict_end_p(it)) { + ViewDict_itref_t* ref = ViewDict_ref(it); + view_free(ref->value); + ViewDict_next(it); + } + ViewDict_clear(view_dispatcher->views); + // Free dispatcher + free(view_dispatcher); +} + +void view_dispatcher_add_view(ViewDispatcher* view_dispatcher, uint32_t view_id, View* view) { + furi_assert(view_dispatcher); + furi_assert(view); + // Check if view id is not used and resgister view + furi_check(ViewDict_get(view_dispatcher->views, view_id) == NULL); + ViewDict_set_at(view_dispatcher->views, view_id, view); + view_set_dispatcher(view, view_dispatcher); +} + +void view_dispatcher_switch_to_view(ViewDispatcher* view_dispatcher, uint32_t view_id) { + furi_assert(view_dispatcher); + if(view_id == VIEW_NONE) { + view_dispatcher->current_view = NULL; + widget_enabled_set(view_dispatcher->widget, false); + } else if(view_id == VIEW_IGNORE) { + } else if(view_id == VIEW_DESTROY) { + view_dispatcher_free(view_dispatcher); + } else { + View** view_pp = ViewDict_get(view_dispatcher->views, view_id); + furi_check(view_pp != NULL); + view_dispatcher->current_view = *view_pp; + widget_enabled_set(view_dispatcher->widget, true); + widget_update(view_dispatcher->widget); + } +} + +void view_dispatcher_attach_to_gui( + ViewDispatcher* view_dispatcher, + Gui* gui, + ViewDispatcherType type) { + furi_assert(view_dispatcher); + furi_assert(view_dispatcher->gui == NULL); + furi_assert(gui); + + if(type == ViewDispatcherTypeNone) { + gui_add_widget(gui, view_dispatcher->widget, GuiLayerNone); + } else if(type == ViewDispatcherTypeFullscreen) { + gui_add_widget(gui, view_dispatcher->widget, GuiLayerFullscreen); + } else if(type == ViewDispatcherTypeWindow) { + gui_add_widget(gui, view_dispatcher->widget, GuiLayerMain); + } else { + furi_check(NULL); + } + view_dispatcher->gui = gui; +} + +void view_dispatcher_draw_callback(Canvas* canvas, void* context) { + ViewDispatcher* view_dispatcher = context; + if(view_dispatcher->current_view) { + view_draw(view_dispatcher->current_view, canvas); + } +} + +void view_dispatcher_input_callback(InputEvent* event, void* context) { + ViewDispatcher* view_dispatcher = context; + bool is_consumed = false; + if(view_dispatcher->current_view) { + is_consumed = view_input(view_dispatcher->current_view, event); + } + if(!is_consumed && event->state) { + uint32_t view_id = VIEW_IGNORE; + if(event->input == InputBack) { + view_id = view_previous(view_dispatcher->current_view); + } else if(event->input == InputOk) { + view_id = view_next(view_dispatcher->current_view); + } + view_dispatcher_switch_to_view(view_dispatcher, view_id); + } +} + +void view_dispatcher_update(ViewDispatcher* view_dispatcher, View* view) { + furi_assert(view_dispatcher); + furi_assert(view); + + if(view_dispatcher->current_view == view) { + widget_update(view_dispatcher->widget); + } +} diff --git a/applications/gui/view_dispatcher.h b/applications/gui/view_dispatcher.h new file mode 100644 index 00000000..e87d4b70 --- /dev/null +++ b/applications/gui/view_dispatcher.h @@ -0,0 +1,45 @@ +#pragma once + +#include "view.h" +#include "gui.h" + +/* ViewDispatcher widget placement */ +typedef enum { + ViewDispatcherTypeNone, /* Special layer for internal use only */ + ViewDispatcherTypeWindow, /* Main widget layer, status bar is shown */ + ViewDispatcherTypeFullscreen /* Fullscreen widget layer */ +} ViewDispatcherType; + +typedef struct ViewDispatcher ViewDispatcher; + +/* Allocate ViewDispatcher + * @return pointer to ViewDispatcher instance + */ +ViewDispatcher* view_dispatcher_alloc(); + +/* Free ViewDispatcher + * @param pointer to View + */ +void view_dispatcher_free(ViewDispatcher* view_dispatcher); + +/* Add view to ViewDispatcher + * @param view_dispatcher, ViewDispatcher instance + * @param view_id, View id to register + * @param view, View instance + */ +void view_dispatcher_add_view(ViewDispatcher* view_dispatcher, uint32_t view_id, View* view); + +/* Switch to View + * @param view_dispatcher, ViewDispatcher instance + * @param view_id, View id to register + */ +void view_dispatcher_switch_to_view(ViewDispatcher* view_dispatcher, uint32_t view_id); + +/* Attach ViewDispatcher to GUI + * @param view_dispatcher, ViewDispatcher instance + * @param gui, GUI instance to attach to + */ +void view_dispatcher_attach_to_gui( + ViewDispatcher* view_dispatcher, + Gui* gui, + ViewDispatcherType type); diff --git a/applications/gui/view_dispatcher_i.h b/applications/gui/view_dispatcher_i.h new file mode 100644 index 00000000..57e20200 --- /dev/null +++ b/applications/gui/view_dispatcher_i.h @@ -0,0 +1,24 @@ +#pragma once + +#include "view_dispatcher.h" +#include "view_i.h" +#include +#include + +DICT_DEF2(ViewDict, uint32_t, M_DEFAULT_OPLIST, View*, M_PTR_OPLIST) + +struct ViewDispatcher { + Gui* gui; + Widget* widget; + ViewDict_t views; + View* current_view; +}; + +/* Widget Draw Callback */ +void view_dispatcher_draw_callback(Canvas* canvas, void* context); + +/* Widget Input Callback */ +void view_dispatcher_input_callback(InputEvent* event, void* context); + +/* View to ViewDispatcher update event */ +void view_dispatcher_update(ViewDispatcher* view_dispatcher, View* view); diff --git a/applications/gui/view_i.h b/applications/gui/view_i.h new file mode 100644 index 00000000..34b0bbb3 --- /dev/null +++ b/applications/gui/view_i.h @@ -0,0 +1,36 @@ +#pragma once + +#include "view.h" +#include "view_dispatcher_i.h" +#include + +typedef struct { + void* data; + osMutexId_t mutex; +} ViewModelLocking; + +struct View { + ViewDispatcher* dispatcher; + ViewDrawCallback draw_callback; + ViewInputCallback input_callback; + ViewModelType model_type; + ViewNavigationCallback previous_callback; + ViewNavigationCallback next_callback; + void* model; + void* context; +}; + +/* Set View dispatcher */ +void view_set_dispatcher(View* view, ViewDispatcher* view_dispatcher); + +/* Draw Callback for View dispatcher */ +void view_draw(View* view, Canvas* canvas); + +/* Input Callback for View dispatcher */ +bool view_input(View* view, InputEvent* event); + +/* Previous Callback for View dispatcher */ +uint32_t view_previous(View* view); + +/* Next Callback for View dispatcher */ +uint32_t view_next(View* view); diff --git a/applications/gui/widget.h b/applications/gui/widget.h index 629d54e6..6cf1f9c4 100644 --- a/applications/gui/widget.h +++ b/applications/gui/widget.h @@ -9,7 +9,7 @@ typedef struct Widget Widget; * Widget Draw callback * @warning called from GUI thread */ -typedef void (*WidgetDrawCallback)(Canvas* api, void* context); +typedef void (*WidgetDrawCallback)(Canvas* canvas, void* context); /* * Widget Input callback diff --git a/applications/power/power.c b/applications/power/power.c index 740c30c3..acb46c8c 100644 --- a/applications/power/power.c +++ b/applications/power/power.c @@ -1,38 +1,33 @@ #include "power.h" +#include "power_views.h" #include #include #include + #include #include +#include +#include + #include #include #include struct Power { + ViewDispatcher* view_dispatcher; + View* info_view; + Icon* usb_icon; Widget* usb_widget; Icon* battery_icon; Widget* battery_widget; - Widget* widget; - ValueMutex* menu_vm; Cli* cli; MenuItem* menu; - - float current_charger; - float current_gauge; - float voltage_charger; - float voltage_gauge; - uint32_t capacity_remaining; - uint32_t capacity_full; - float temperature_charger; - float temperature_gauge; - - uint8_t charge; }; void power_draw_usb_callback(Canvas* canvas, void* context) { @@ -46,70 +41,31 @@ void power_draw_battery_callback(Canvas* canvas, void* context) { Power* power = context; canvas_draw_icon(canvas, 0, 0, power->battery_icon); - canvas_draw_box(canvas, 2, 2, (float)power->charge / 100 * 14, 4); + with_view_model( + power->info_view, (PowerInfoModel * model) { + canvas_draw_box(canvas, 2, 2, (float)model->charge / 100 * 14, 4); + }); } -void power_off_callback(void* context) { +void power_menu_off_callback(void* context) { api_hal_power_off(); } -void power_enable_otg_callback(void* context) { +void power_menu_reset_callback(void* context) { + NVIC_SystemReset(); +} + +void power_menu_enable_otg_callback(void* context) { api_hal_power_enable_otg(); } -void power_disable_otg_callback(void* context) { +void power_menu_disable_otg_callback(void* context) { api_hal_power_disable_otg(); } -void power_info_callback(void* context) { +void power_menu_info_callback(void* context) { Power* power = context; - widget_enabled_set(power->widget, true); -} - -void power_draw_callback(Canvas* canvas, void* context) { - Power* power = context; - - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 2, 10, "Power state:"); - - char buffer[64]; - canvas_set_font(canvas, FontSecondary); - snprintf( - buffer, - 64, - "Current: %ld/%ldmA", - (int32_t)(power->current_gauge * 1000), - (int32_t)(power->current_charger * 1000)); - canvas_draw_str(canvas, 5, 22, buffer); - snprintf( - buffer, - 64, - "Voltage: %ld/%ldmV", - (uint32_t)(power->voltage_gauge * 1000), - (uint32_t)(power->voltage_charger * 1000)); - canvas_draw_str(canvas, 5, 32, buffer); - snprintf(buffer, 64, "Charge: %ld%%", (uint32_t)(power->charge)); - canvas_draw_str(canvas, 5, 42, buffer); - snprintf( - buffer, 64, "Capacity: %ld of %ldmAh", power->capacity_remaining, power->capacity_full); - canvas_draw_str(canvas, 5, 52, buffer); - snprintf( - buffer, - 64, - "Temperature: %ld/%ldC", - (uint32_t)(power->temperature_gauge), - (uint32_t)(power->temperature_charger)); - canvas_draw_str(canvas, 5, 62, buffer); -} - -void power_input_callback(InputEvent* event, void* context) { - Power* power = context; - - if(!event->state || event->input != InputBack) return; - - widget_enabled_set(power->widget, false); + view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewInfo); } Power* power_alloc() { @@ -122,26 +78,30 @@ Power* power_alloc() { power->menu = menu_item_alloc_menu("Power", NULL); menu_item_subitem_add( - power->menu, menu_item_alloc_function("Poweroff", NULL, power_off_callback, power)); + power->menu, menu_item_alloc_function("Off", NULL, power_menu_off_callback, power)); + menu_item_subitem_add( + power->menu, menu_item_alloc_function("Reset", NULL, power_menu_reset_callback, power)); menu_item_subitem_add( power->menu, - menu_item_alloc_function("Enable OTG", NULL, power_enable_otg_callback, power)); + menu_item_alloc_function("Enable OTG", NULL, power_menu_enable_otg_callback, power)); menu_item_subitem_add( power->menu, - menu_item_alloc_function("Disable OTG", NULL, power_disable_otg_callback, power)); + menu_item_alloc_function("Disable OTG", NULL, power_menu_disable_otg_callback, power)); menu_item_subitem_add( - power->menu, menu_item_alloc_function("Info", NULL, power_info_callback, power)); + power->menu, menu_item_alloc_function("Info", NULL, power_menu_info_callback, power)); + + power->view_dispatcher = view_dispatcher_alloc(); + power->info_view = view_alloc(); + view_allocate_model(power->info_view, ViewModelTypeLockFree, sizeof(PowerInfoModel)); + view_set_draw_callback(power->info_view, power_info_draw_callback); + view_set_previous_callback(power->info_view, power_info_back_callback); + view_dispatcher_add_view(power->view_dispatcher, PowerViewInfo, power->info_view); power->usb_icon = assets_icons_get(I_USBConnected_15x8); power->usb_widget = widget_alloc(); widget_set_width(power->usb_widget, icon_get_width(power->usb_icon)); widget_draw_callback_set(power->usb_widget, power_draw_usb_callback, power); - power->widget = widget_alloc(); - widget_draw_callback_set(power->widget, power_draw_callback, power); - widget_input_callback_set(power->widget, power_input_callback, power); - widget_enabled_set(power->widget, false); - power->battery_icon = assets_icons_get(I_Battery_19x8); power->battery_widget = widget_alloc(); widget_set_width(power->battery_widget, icon_get_width(power->battery_icon)); @@ -204,9 +164,9 @@ void power_task(void* p) { } Gui* gui = furi_open("gui"); - gui_add_widget(gui, power->widget, GuiLayerFullscreen); gui_add_widget(gui, power->usb_widget, GuiLayerStatusBarLeft); gui_add_widget(gui, power->battery_widget, GuiLayerStatusBarRight); + view_dispatcher_attach_to_gui(power->view_dispatcher, gui, ViewDispatcherTypeFullscreen); with_value_mutex( power->menu_vm, (Menu * menu) { menu_item_add(menu, power->menu); }); @@ -221,16 +181,22 @@ void power_task(void* p) { furiac_ready(); while(1) { - power->charge = api_hal_power_get_pct(); - power->capacity_remaining = api_hal_power_get_battery_remaining_capacity(); - power->capacity_full = api_hal_power_get_battery_full_capacity(); - power->current_charger = api_hal_power_get_battery_current(ApiHalPowerICCharger); - power->current_gauge = api_hal_power_get_battery_current(ApiHalPowerICFuelGauge); - power->voltage_charger = api_hal_power_get_battery_voltage(ApiHalPowerICCharger); - power->voltage_gauge = api_hal_power_get_battery_voltage(ApiHalPowerICFuelGauge); - power->temperature_charger = api_hal_power_get_battery_temperature(ApiHalPowerICCharger); - power->temperature_gauge = api_hal_power_get_battery_temperature(ApiHalPowerICFuelGauge); - widget_update(power->widget); + with_view_model( + power->info_view, (PowerInfoModel * model) { + model->charge = api_hal_power_get_pct(); + model->capacity_remaining = api_hal_power_get_battery_remaining_capacity(); + model->capacity_full = api_hal_power_get_battery_full_capacity(); + model->current_charger = api_hal_power_get_battery_current(ApiHalPowerICCharger); + model->current_gauge = api_hal_power_get_battery_current(ApiHalPowerICFuelGauge); + model->voltage_charger = api_hal_power_get_battery_voltage(ApiHalPowerICCharger); + model->voltage_gauge = api_hal_power_get_battery_voltage(ApiHalPowerICFuelGauge); + model->temperature_charger = + api_hal_power_get_battery_temperature(ApiHalPowerICCharger); + model->temperature_gauge = + api_hal_power_get_battery_temperature(ApiHalPowerICFuelGauge); + }); + + widget_update(power->battery_widget); widget_enabled_set(power->usb_widget, api_hal_power_is_charging()); osDelay(1000); } diff --git a/applications/power/power_views.c b/applications/power/power_views.c new file mode 100644 index 00000000..1f0dfeb2 --- /dev/null +++ b/applications/power/power_views.c @@ -0,0 +1,38 @@ +#include "power_views.h" + +void power_info_draw_callback(Canvas* canvas, void* context) { + PowerInfoModel* data = context; + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 2, 10, "Power state:"); + + char buffer[64]; + canvas_set_font(canvas, FontSecondary); + snprintf( + buffer, + 64, + "Current: %ld/%ldmA", + (int32_t)(data->current_gauge * 1000), + (int32_t)(data->current_charger * 1000)); + canvas_draw_str(canvas, 5, 22, buffer); + snprintf( + buffer, + 64, + "Voltage: %ld/%ldmV", + (uint32_t)(data->voltage_gauge * 1000), + (uint32_t)(data->voltage_charger * 1000)); + canvas_draw_str(canvas, 5, 32, buffer); + snprintf(buffer, 64, "Charge: %ld%%", (uint32_t)(data->charge)); + canvas_draw_str(canvas, 5, 42, buffer); + snprintf(buffer, 64, "Capacity: %ld of %ldmAh", data->capacity_remaining, data->capacity_full); + canvas_draw_str(canvas, 5, 52, buffer); + snprintf( + buffer, + 64, + "Temperature: %ld/%ldC", + (uint32_t)(data->temperature_gauge), + (uint32_t)(data->temperature_charger)); + canvas_draw_str(canvas, 5, 62, buffer); +} diff --git a/applications/power/power_views.h b/applications/power/power_views.h new file mode 100644 index 00000000..97931601 --- /dev/null +++ b/applications/power/power_views.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include + +typedef enum { PowerViewInfo } PowerView; + +typedef struct { + float current_charger; + float current_gauge; + + float voltage_charger; + float voltage_gauge; + + uint32_t capacity_remaining; + uint32_t capacity_full; + + float temperature_charger; + float temperature_gauge; + + uint8_t charge; +} PowerInfoModel; + +static uint32_t power_info_back_callback(void* context) { + return VIEW_NONE; +} + +void power_info_draw_callback(Canvas* canvas, void* context); diff --git a/assets/assets.py b/assets/assets.py index 565589f3..73852087 100755 --- a/assets/assets.py +++ b/assets/assets.py @@ -21,7 +21,14 @@ ICONS_TEMPLATE_H_FOOTER = """} IconName; Icon * assets_icons_get(IconName name); """ -ICONS_TEMPLATE_C_HEADER = """#include \"assets_icons.h\" +ICONS_TEMPLATE_H_I = """#pragma once + +#include + +const IconData * assets_icons_get_data(IconName name); +""" + +ICONS_TEMPLATE_C_HEADER = """#include \"assets_icons_i.h\" #include """ @@ -31,9 +38,12 @@ ICONS_TEMPLATE_C_ICONS_ARRAY_START = "const IconData icons[] = {\n" ICONS_TEMPLATE_C_ICONS_ITEM = "\t{{ .width={width}, .height={height}, .frame_count={frame_count}, .frame_rate={frame_rate}, .frames=_{name} }},\n" ICONS_TEMPLATE_C_ICONS_ARRAY_END = "};" ICONS_TEMPLATE_C_FOOTER = """ +const IconData * assets_icons_get_data(IconName name) { + return &icons[name]; +} Icon * assets_icons_get(IconName name) { - return icon_alloc(&icons[name]); + return icon_alloc(assets_icons_get_data(name)); } """ @@ -157,13 +167,18 @@ class Assets: icons_c.write(ICONS_TEMPLATE_C_ICONS_ARRAY_END) icons_c.write(ICONS_TEMPLATE_C_FOOTER) icons_c.write("\n") - # Create Header + # Create Public Header self.logger.debug(f"Creating header") icons_h = open(os.path.join(self.args.output_directory, "assets_icons.h"), "w") icons_h.write(ICONS_TEMPLATE_H_HEADER) for name, width, height, frame_rate, frame_count in icons: icons_h.write(ICONS_TEMPLATE_H_ICON_NAME.format(name=name)) icons_h.write(ICONS_TEMPLATE_H_FOOTER) + # Create Private Header + icons_h_i = open( + os.path.join(self.args.output_directory, "assets_icons_i.h"), "w" + ) + icons_h_i.write(ICONS_TEMPLATE_H_I) self.logger.debug(f"Done") def icon2header(self, file): diff --git a/assets/icons/Dolphin/DolphinFirstStart0_128x54.png b/assets/icons/Dolphin/DolphinFirstStart0_128x54.png new file mode 100644 index 00000000..a084b127 Binary files /dev/null and b/assets/icons/Dolphin/DolphinFirstStart0_128x54.png differ diff --git a/assets/icons/Dolphin/DolphinFirstStart1_128x54.png b/assets/icons/Dolphin/DolphinFirstStart1_128x54.png new file mode 100644 index 00000000..87ae4c33 Binary files /dev/null and b/assets/icons/Dolphin/DolphinFirstStart1_128x54.png differ diff --git a/assets/icons/Dolphin/DolphinFirstStart2_128x54.png b/assets/icons/Dolphin/DolphinFirstStart2_128x54.png new file mode 100644 index 00000000..7a67a021 Binary files /dev/null and b/assets/icons/Dolphin/DolphinFirstStart2_128x54.png differ diff --git a/assets/icons/Dolphin/DolphinFirstStart3_128x54.png b/assets/icons/Dolphin/DolphinFirstStart3_128x54.png new file mode 100644 index 00000000..317df65b Binary files /dev/null and b/assets/icons/Dolphin/DolphinFirstStart3_128x54.png differ diff --git a/assets/icons/Dolphin/DolphinFirstStart4_128x54.png b/assets/icons/Dolphin/DolphinFirstStart4_128x54.png new file mode 100644 index 00000000..db3ba71f Binary files /dev/null and b/assets/icons/Dolphin/DolphinFirstStart4_128x54.png differ diff --git a/assets/icons/Dolphin/DolphinFirstStart5_128x54.png b/assets/icons/Dolphin/DolphinFirstStart5_128x54.png new file mode 100644 index 00000000..6fed3ce8 Binary files /dev/null and b/assets/icons/Dolphin/DolphinFirstStart5_128x54.png differ diff --git a/assets/icons/Dolphin/DolphinFirstStart6_128x54.png b/assets/icons/Dolphin/DolphinFirstStart6_128x54.png new file mode 100644 index 00000000..07913a8b Binary files /dev/null and b/assets/icons/Dolphin/DolphinFirstStart6_128x54.png differ diff --git a/assets/icons/Dolphin/DolphinFirstStart7_128x54.png b/assets/icons/Dolphin/DolphinFirstStart7_128x54.png new file mode 100644 index 00000000..35ebf277 Binary files /dev/null and b/assets/icons/Dolphin/DolphinFirstStart7_128x54.png differ diff --git a/assets/icons/Dolphin/DolphinFirstStart8_128x54.png b/assets/icons/Dolphin/DolphinFirstStart8_128x54.png new file mode 100644 index 00000000..bac0d082 Binary files /dev/null and b/assets/icons/Dolphin/DolphinFirstStart8_128x54.png differ diff --git a/firmware/targets/f4/Src/main.c b/firmware/targets/f4/Src/main.c index 7aff506f..9e4ef285 100644 --- a/firmware/targets/f4/Src/main.c +++ b/firmware/targets/f4/Src/main.c @@ -122,6 +122,8 @@ int main(void) delay_us_init_DWT(); api_hal_vcp_init(); api_hal_spi_init(); + // Errata 2.2.9, Flash OPTVERR flag is always set after system reset + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); /* USER CODE END 2 */ /* Init scheduler */ diff --git a/firmware/targets/f4/api-hal/api-hal-flash.c b/firmware/targets/f4/api-hal/api-hal-flash.c index ef8f0b97..557c81a8 100644 --- a/firmware/targets/f4/api-hal/api-hal-flash.c +++ b/firmware/targets/f4/api-hal/api-hal-flash.c @@ -2,14 +2,28 @@ #include #include -void api_hal_flash_write_dword(size_t address, uint64_t data) { +bool api_hal_flash_erase(uint8_t page, uint8_t count) { api_hal_bt_lock_flash(); - HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, data); + FLASH_EraseInitTypeDef erase; + erase.TypeErase = FLASH_TYPEERASE_PAGES; + erase.Page = page; + erase.NbPages = count; + uint32_t error; + HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&erase, &error); api_hal_bt_unlock_flash(); + return status == HAL_OK; } -void api_hal_flash_write_row(size_t address, size_t source_address) { +bool api_hal_flash_write_dword(size_t address, uint64_t data) { api_hal_bt_lock_flash(); - HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, source_address); + HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, data); api_hal_bt_unlock_flash(); + return status == HAL_OK; +} + +bool api_hal_flash_write_row(size_t address, size_t source_address) { + api_hal_bt_lock_flash(); + HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FAST, address, source_address); + api_hal_bt_unlock_flash(); + return status == HAL_OK; } diff --git a/firmware/targets/f4/api-hal/api-hal-flash.h b/firmware/targets/f4/api-hal/api-hal-flash.h index ce7247c6..2b155322 100644 --- a/firmware/targets/f4/api-hal/api-hal-flash.h +++ b/firmware/targets/f4/api-hal/api-hal-flash.h @@ -1,15 +1,24 @@ #pragma once +#include #include #include +/* + * Erase Flash + * Locking operation, uses HSEM to manage shared access. + * @param page, page number + * @param count, page count to erase + */ +bool api_hal_flash_erase(uint8_t page, uint8_t count); + /* * Write double word (64 bits) * Locking operation, uses HSEM to manage shared access. * @param address - destination address, must be double word aligned. * @param data - data to write */ -void api_hal_flash_write_dword(size_t address, uint64_t data); +bool api_hal_flash_write_dword(size_t address, uint64_t data); /* * Write page (4096 bytes or 64 rows of double words). @@ -17,4 +26,4 @@ void api_hal_flash_write_dword(size_t address, uint64_t data); * @param address - destination address, must be page aligned * @param source_address - source address */ -void api_hal_flash_write_page(size_t address, size_t source_address); +bool api_hal_flash_write_page(size_t address, size_t source_address); diff --git a/make/rules.mk b/make/rules.mk index d4614ce1..d25f5ce2 100644 --- a/make/rules.mk +++ b/make/rules.mk @@ -62,7 +62,7 @@ $(OBJ_DIR)/upload: $(OBJ_DIR)/$(PROJECT).bin dfu-util -D $(OBJ_DIR)/$(PROJECT).bin -a 0 -s $(FLASH_ADDRESS) -S $(DFU_SERIAL) touch $@ -$(ASSETS): $(ASSETS_SOURCES) +$(ASSETS): $(ASSETS_SOURCES) $(ASSETS_COMPILLER) @echo "\tASSETS\t" $@ @$(ASSETS_COMPILLER) icons -s $(ASSETS_SOURCE_DIR) -o $(ASSETS_OUTPUT_DIR)