[AVR_ISP]: add AVR ISP Programmer FAP (#2475)

* [AVR_ISP]: add AVR ISP Programmer FAP
* [AVR_ISP]: add auto detect AVR chip
* [AVR_ISP]: fix auto detect chip
* [AVR_ISP]: fix fast write flash
* AVR_ISP: auto set SPI speed
* AVR_ISP: add clock 4Mhz on &gpio_ext_pa4
* AVR_ISP: fix "[CRASH][ISR 4] NULL pointer dereference" with no AVR chip connected
* AVR_ISP: add AVR ISP Reader
* AVR_ISP: add read and check I32HEX file
* AVR_ISP: add write eerom, flash, fuse, lock byte
* AVR_ISP: add gui Reader, Writer
* Github: unshallow on decontamination
* AVR_ISP: move to external
* API: fix api_symbols
* AVR_ISP: add wiring scene
* GUI: model mutex FuriMutexTypeNormal -> FuriMutexTypeRecursive
* AVR_ISP: add chip_detect view
* AVR_ISP: refactoring gui ISP Programmer
* AVR_ISP: add gui "Dump AVR"
* AVR_ISP: add gui "Flash AVR"
* AVR_ISP: fix navigation gui
* GUI: model mutex FuriMutexTypeRecursive -> FuriMutexTypeNormal
* AVR_ISP: fix conflicts
* AVR_ISP: fix build
* AVR_ISP: delete images
* AVR_ISP: add images
* AVR_ISP: fix gui
* AVR_ISP: fix stuck in navigation
* AVR_ISP:  changing the Fuse bit recording logic
* AVR_ISP: fix read/write chips with memory greater than 64Kb
* AVR_ISP: fix auto set speed SPI
* AVR_ISP: fix gui
* ISP: switching on +5 volts to an external GPIO

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
Skorpionm
2023-04-06 08:13:30 +04:00
committed by GitHub
parent b4ceb55fd2
commit d1ad924216
56 changed files with 5709 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
#include "../avr_isp_app_i.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const avr_isp_scene_on_enter_handlers[])(void*) = {
#include "avr_isp_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_event handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const avr_isp_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "avr_isp_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_exit handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const avr_isp_scene_on_exit_handlers[])(void* context) = {
#include "avr_isp_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers avr_isp_scene_handlers = {
.on_enter_handlers = avr_isp_scene_on_enter_handlers,
.on_event_handlers = avr_isp_scene_on_event_handlers,
.on_exit_handlers = avr_isp_scene_on_exit_handlers,
.scene_num = AvrIspSceneNum,
};

View File

@@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) AvrIspScene##id,
typedef enum {
#include "avr_isp_scene_config.h"
AvrIspSceneNum,
} AvrIspScene;
#undef ADD_SCENE
extern const SceneManagerHandlers avr_isp_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "avr_isp_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
#include "avr_isp_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
#include "avr_isp_scene_config.h"
#undef ADD_SCENE

View File

@@ -0,0 +1,99 @@
#include "../avr_isp_app_i.h"
#include "../helpers/avr_isp_types.h"
void avr_isp_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) {
furi_assert(context);
AvrIspApp* app = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(app->view_dispatcher, result);
}
}
void avr_isp_scene_about_on_enter(void* context) {
furi_assert(context);
AvrIspApp* app = context;
FuriString* temp_str = furi_string_alloc();
furi_string_printf(temp_str, "\e#%s\n", "Information");
furi_string_cat_printf(temp_str, "Version: %s\n", AVR_ISP_VERSION_APP);
furi_string_cat_printf(temp_str, "Developed by: %s\n", AVR_ISP_DEVELOPED);
furi_string_cat_printf(temp_str, "Github: %s\n\n", AVR_ISP_GITHUB);
furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
furi_string_cat_printf(
temp_str,
"This application is an AVR in-system programmer based on stk500mk1. It is compatible with AVR-based"
" microcontrollers including Arduino. You can also use it to repair the chip if you accidentally"
" corrupt the bootloader.\n\n");
furi_string_cat_printf(temp_str, "\e#%s\n", "What it can do:");
furi_string_cat_printf(temp_str, "- Create a dump of your chip on an SD card\n");
furi_string_cat_printf(temp_str, "- Flash your chip firmware from the SD card\n");
furi_string_cat_printf(temp_str, "- Act as a wired USB ISP using avrdude software\n\n");
furi_string_cat_printf(temp_str, "\e#%s\n", "Supported chip series:");
furi_string_cat_printf(
temp_str,
"Example command for avrdude flashing: avrdude.exe -p m328p -c stk500v1 -P COMxx -U flash:r:"
"X:\\sketch_sample.hex"
":i\n");
furi_string_cat_printf(
temp_str,
"Where: "
"-p m328p"
" brand of your chip, "
"-P COMxx"
" com port number in the system when "
"ISP Programmer"
" is enabled\n\n");
furi_string_cat_printf(temp_str, "\e#%s\n", "Info");
furi_string_cat_printf(
temp_str,
"ATtinyXXXX\nATmegaXXXX\nAT43Uxxx\nAT76C711\nAT86RF401\nAT90xxxxx\nAT94K\n"
"ATAxxxxx\nATA664251\nM3000\nLGT8F88P\nLGT8F168P\nLGT8F328P\n");
furi_string_cat_printf(
temp_str, "For a more detailed list of supported chips, see AVRDude help\n");
widget_add_text_box_element(
app->widget,
0,
0,
128,
14,
AlignCenter,
AlignBottom,
"\e#\e! \e!\n",
false);
widget_add_text_box_element(
app->widget,
0,
2,
128,
14,
AlignCenter,
AlignBottom,
"\e#\e! ISP Programmer \e!\n",
false);
widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget);
}
bool avr_isp_scene_about_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void avr_isp_scene_about_on_exit(void* context) {
furi_assert(context);
AvrIspApp* app = context;
// Clear views
widget_reset(app->widget);
}

View File

@@ -0,0 +1,72 @@
#include "../avr_isp_app_i.h"
void avr_isp_scene_chip_detect_callback(AvrIspCustomEvent event, void* context) {
furi_assert(context);
AvrIspApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, event);
}
void avr_isp_scene_chip_detect_on_enter(void* context) {
furi_assert(context);
AvrIspApp* app = context;
switch(app->error) {
case AvrIspErrorReading:
case AvrIspErrorWriting:
case AvrIspErrorWritingFuse:
avr_isp_chip_detect_set_state(
app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorOccured);
break;
case AvrIspErrorVerification:
avr_isp_chip_detect_set_state(
app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorVerification);
break;
default:
avr_isp_chip_detect_set_state(
app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateNoDetect);
break;
}
app->error = AvrIspErrorNoError;
avr_isp_chip_detect_view_set_callback(
app->avr_isp_chip_detect_view, avr_isp_scene_chip_detect_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewChipDetect);
}
bool avr_isp_scene_chip_detect_on_event(void* context, SceneManagerEvent event) {
furi_assert(context);
AvrIspApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case AvrIspCustomEventSceneChipDetectOk:
if(scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
AvrIspViewProgrammer) {
scene_manager_next_scene(app->scene_manager, AvrIspSceneProgrammer);
} else if(
scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
AvrIspViewReader) {
scene_manager_next_scene(app->scene_manager, AvrIspSceneInputName);
} else if(
scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
AvrIspViewWriter) {
scene_manager_next_scene(app->scene_manager, AvrIspSceneLoad);
}
consumed = true;
break;
default:
break;
}
} else if(event.type == SceneManagerEventTypeTick) {
}
return consumed;
}
void avr_isp_scene_chip_detect_on_exit(void* context) {
UNUSED(context);
}

View File

@@ -0,0 +1,10 @@
ADD_SCENE(avr_isp, start, Start)
ADD_SCENE(avr_isp, about, About)
ADD_SCENE(avr_isp, programmer, Programmer)
ADD_SCENE(avr_isp, reader, Reader)
ADD_SCENE(avr_isp, input_name, InputName)
ADD_SCENE(avr_isp, load, Load)
ADD_SCENE(avr_isp, writer, Writer)
ADD_SCENE(avr_isp, wiring, Wiring)
ADD_SCENE(avr_isp, chip_detect, ChipDetect)
ADD_SCENE(avr_isp, success, Success)

View File

@@ -0,0 +1,89 @@
#include "../avr_isp_app_i.h"
#include <gui/modules/validators.h>
#define MAX_TEXT_INPUT_LEN 22
void avr_isp_scene_input_name_text_callback(void* context) {
furi_assert(context);
AvrIspApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneInputName);
}
void avr_isp_scene_input_name_get_timefilename(FuriString* name) {
FuriHalRtcDateTime datetime = {0};
furi_hal_rtc_get_datetime(&datetime);
furi_string_printf(
name,
"AVR_dump-%.4d%.2d%.2d-%.2d%.2d%.2d",
datetime.year,
datetime.month,
datetime.day,
datetime.hour,
datetime.minute,
datetime.second);
}
void avr_isp_scene_input_name_on_enter(void* context) {
furi_assert(context);
AvrIspApp* app = context;
// Setup view
TextInput* text_input = app->text_input;
bool dev_name_empty = false;
FuriString* file_name = furi_string_alloc();
avr_isp_scene_input_name_get_timefilename(file_name);
furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX);
//highlighting the entire filename by default
dev_name_empty = true;
strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME);
text_input_set_header_text(text_input, "Name dump");
text_input_set_result_callback(
text_input,
avr_isp_scene_input_name_text_callback,
app,
app->file_name_tmp,
MAX_TEXT_INPUT_LEN, // buffer size
dev_name_empty);
ValidatorIsFile* validator_is_file =
validator_is_file_alloc_init(STORAGE_APP_DATA_PATH_PREFIX, AVR_ISP_APP_EXTENSION, "");
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
furi_string_free(file_name);
view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewTextInput);
}
bool avr_isp_scene_input_name_on_event(void* context, SceneManagerEvent event) {
furi_assert(context);
AvrIspApp* app = context;
if(event.type == SceneManagerEventTypeBack) {
scene_manager_previous_scene(app->scene_manager);
return true;
} else if(event.type == SceneManagerEventTypeCustom) {
if(event.event == AvrIspCustomEventSceneInputName) {
if(strcmp(app->file_name_tmp, "") != 0) {
scene_manager_next_scene(app->scene_manager, AvrIspSceneReader);
} else {
}
}
}
return false;
}
void avr_isp_scene_input_name_on_exit(void* context) {
furi_assert(context);
AvrIspApp* app = context;
// Clear validator
void* validator_context = text_input_get_validator_callback_context(app->text_input);
text_input_set_validator(app->text_input, NULL, NULL);
validator_is_file_free(validator_context);
// Clear view
text_input_reset(app->text_input);
}

View File

@@ -0,0 +1,22 @@
#include "../avr_isp_app_i.h"
void avr_isp_scene_load_on_enter(void* context) {
furi_assert(context);
AvrIspApp* app = context;
if(avr_isp_load_from_file(app)) {
scene_manager_next_scene(app->scene_manager, AvrIspSceneWriter);
} else {
scene_manager_previous_scene(app->scene_manager);
}
}
bool avr_isp_scene_load_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void avr_isp_scene_load_on_exit(void* context) {
UNUSED(context);
}

View File

@@ -0,0 +1,28 @@
#include "../avr_isp_app_i.h"
void avr_isp_scene_programmer_callback(AvrIspCustomEvent event, void* context) {
furi_assert(context);
AvrIspApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, event);
}
void avr_isp_scene_programmer_on_enter(void* context) {
furi_assert(context);
AvrIspApp* app = context;
avr_isp_programmer_view_set_callback(
app->avr_isp_programmer_view, avr_isp_scene_programmer_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewProgrammer);
}
bool avr_isp_scene_programmer_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void avr_isp_scene_programmer_on_exit(void* context) {
UNUSED(context);
}

View File

@@ -0,0 +1,64 @@
#include "../avr_isp_app_i.h"
void avr_isp_scene_reader_callback(AvrIspCustomEvent event, void* context) {
furi_assert(context);
AvrIspApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, event);
}
void avr_isp_scene_reader_on_enter(void* context) {
furi_assert(context);
AvrIspApp* app = context;
avr_isp_reader_set_file_path(
app->avr_isp_reader_view, furi_string_get_cstr(app->file_path), app->file_name_tmp);
avr_isp_reader_view_set_callback(app->avr_isp_reader_view, avr_isp_scene_reader_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewReader);
}
bool avr_isp_scene_reader_on_event(void* context, SceneManagerEvent event) {
furi_assert(context);
AvrIspApp* app = context;
UNUSED(app);
bool consumed = false;
if(event.type == SceneManagerEventTypeBack) {
//do not handle exit on "Back"
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case AvrIspCustomEventSceneReadingOk:
scene_manager_next_scene(app->scene_manager, AvrIspSceneSuccess);
consumed = true;
break;
case AvrIspCustomEventSceneExit:
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, AvrIspSceneChipDetect);
consumed = true;
break;
case AvrIspCustomEventSceneErrorVerification:
app->error = AvrIspErrorVerification;
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, AvrIspSceneChipDetect);
consumed = true;
break;
case AvrIspCustomEventSceneErrorReading:
app->error = AvrIspErrorReading;
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, AvrIspSceneChipDetect);
consumed = true;
break;
default:
break;
}
} else if(event.type == SceneManagerEventTypeTick) {
avr_isp_reader_update_progress(app->avr_isp_reader_view);
}
return consumed;
}
void avr_isp_scene_reader_on_exit(void* context) {
UNUSED(context);
}

View File

@@ -0,0 +1,75 @@
#include "../avr_isp_app_i.h"
void avr_isp_scene_start_submenu_callback(void* context, uint32_t index) {
furi_assert(context);
AvrIspApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void avr_isp_scene_start_on_enter(void* context) {
furi_assert(context);
AvrIspApp* app = context;
Submenu* submenu = app->submenu;
submenu_add_item(
submenu, "Dump AVR", SubmenuIndexAvrIspReader, avr_isp_scene_start_submenu_callback, app);
submenu_add_item(
submenu, "Flash AVR", SubmenuIndexAvrIspWriter, avr_isp_scene_start_submenu_callback, app);
submenu_add_item(
submenu,
"ISP Programmer",
SubmenuIndexAvrIspProgrammer,
avr_isp_scene_start_submenu_callback,
app);
submenu_add_item(
submenu, "Wiring", SubmenuIndexAvrIsWiring, avr_isp_scene_start_submenu_callback, app);
submenu_add_item(
submenu, "About", SubmenuIndexAvrIspAbout, avr_isp_scene_start_submenu_callback, app);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(app->scene_manager, AvrIspSceneStart));
view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewSubmenu);
}
bool avr_isp_scene_start_on_event(void* context, SceneManagerEvent event) {
furi_assert(context);
AvrIspApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexAvrIspAbout) {
scene_manager_next_scene(app->scene_manager, AvrIspSceneAbout);
consumed = true;
} else if(event.event == SubmenuIndexAvrIspProgrammer) {
scene_manager_set_scene_state(
app->scene_manager, AvrIspSceneChipDetect, AvrIspViewProgrammer);
scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
consumed = true;
} else if(event.event == SubmenuIndexAvrIspReader) {
scene_manager_set_scene_state(
app->scene_manager, AvrIspSceneChipDetect, AvrIspViewReader);
scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
consumed = true;
} else if(event.event == SubmenuIndexAvrIspWriter) {
scene_manager_set_scene_state(
app->scene_manager, AvrIspSceneChipDetect, AvrIspViewWriter);
scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
consumed = true;
} else if(event.event == SubmenuIndexAvrIsWiring) {
scene_manager_next_scene(app->scene_manager, AvrIspSceneWiring);
consumed = true;
}
scene_manager_set_scene_state(app->scene_manager, AvrIspSceneStart, event.event);
}
return consumed;
}
void avr_isp_scene_start_on_exit(void* context) {
furi_assert(context);
AvrIspApp* app = context;
submenu_reset(app->submenu);
}

View File

@@ -0,0 +1,44 @@
#include "../avr_isp_app_i.h"
void avr_isp_scene_success_popup_callback(void* context) {
furi_assert(context);
AvrIspApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneSuccess);
}
void avr_isp_scene_success_on_enter(void* context) {
furi_assert(context);
AvrIspApp* app = context;
Popup* popup = app->popup;
popup_set_icon(popup, 32, 5, &I_dolphin_nice_96x59);
popup_set_header(popup, "Success!", 8, 22, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500);
popup_set_context(popup, app);
popup_set_callback(popup, avr_isp_scene_success_popup_callback);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewPopup);
}
bool avr_isp_scene_success_on_event(void* context, SceneManagerEvent event) {
furi_assert(context);
AvrIspApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == AvrIspCustomEventSceneSuccess) {
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, AvrIspSceneStart);
return true;
}
}
return false;
}
void avr_isp_scene_success_on_exit(void* context) {
furi_assert(context);
AvrIspApp* app = context;
Popup* popup = app->popup;
popup_reset(popup);
}

View File

@@ -0,0 +1,21 @@
#include "../avr_isp_app_i.h"
void avr_isp_scene_wiring_on_enter(void* context) {
furi_assert(context);
AvrIspApp* app = context;
widget_add_icon_element(app->widget, 0, 0, &I_avr_wiring);
view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget);
}
bool avr_isp_scene_wiring_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void avr_isp_scene_wiring_on_exit(void* context) {
furi_assert(context);
AvrIspApp* app = context;
widget_reset(app->widget);
}

View File

@@ -0,0 +1,69 @@
#include "../avr_isp_app_i.h"
void avr_isp_scene_writer_callback(AvrIspCustomEvent event, void* context) {
furi_assert(context);
AvrIspApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, event);
}
void avr_isp_scene_writer_on_enter(void* context) {
furi_assert(context);
AvrIspApp* app = context;
avr_isp_writer_set_file_path(
app->avr_isp_writer_view, furi_string_get_cstr(app->file_path), app->file_name_tmp);
avr_isp_writer_view_set_callback(app->avr_isp_writer_view, avr_isp_scene_writer_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWriter);
}
bool avr_isp_scene_writer_on_event(void* context, SceneManagerEvent event) {
furi_assert(context);
AvrIspApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeBack) {
//do not handle exit on "Back"
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case AvrIspCustomEventSceneExitStartMenu:
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, AvrIspSceneStart);
consumed = true;
break;
case AvrIspCustomEventSceneExit:
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, AvrIspSceneChipDetect);
consumed = true;
break;
case AvrIspCustomEventSceneErrorVerification:
app->error = AvrIspErrorVerification;
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, AvrIspSceneChipDetect);
consumed = true;
break;
case AvrIspCustomEventSceneErrorWriting:
app->error = AvrIspErrorWriting;
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, AvrIspSceneChipDetect);
consumed = true;
break;
case AvrIspCustomEventSceneErrorWritingFuse:
app->error = AvrIspErrorWritingFuse;
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, AvrIspSceneChipDetect);
consumed = true;
break;
default:
break;
}
} else if(event.type == SceneManagerEventTypeTick) {
avr_isp_writer_update_progress(app->avr_isp_writer_view);
}
return consumed;
}
void avr_isp_scene_writer_on_exit(void* context) {
UNUSED(context);
}