From 95bd038d8a7380b8f2db4b968e56c74aa212278b Mon Sep 17 00:00:00 2001 From: kallanreed Date: Mon, 3 Apr 2023 17:40:16 -0700 Subject: [PATCH 01/32] Add ir_scope external app. --- .../external/ir_scope/application.fam | 11 + applications/external/ir_scope/ir_scope.c | 208 ++++++++++++++++++ applications/external/ir_scope/ir_scope.png | Bin 0 -> 169 bytes 3 files changed, 219 insertions(+) create mode 100644 applications/external/ir_scope/application.fam create mode 100644 applications/external/ir_scope/ir_scope.c create mode 100644 applications/external/ir_scope/ir_scope.png diff --git a/applications/external/ir_scope/application.fam b/applications/external/ir_scope/application.fam new file mode 100644 index 0000000000..f99e14515d --- /dev/null +++ b/applications/external/ir_scope/application.fam @@ -0,0 +1,11 @@ +App( + appid="ir_scope", + name="IR Scope", + apptype=FlipperAppType.EXTERNAL, + entry_point="ir_scope_app", + cdefines=["APP_IR_SCOPE"], + requires=["gui"], + stack_size=2 * 1024, + fap_icon="ir_scope.png", + fap_category="Tools", +) diff --git a/applications/external/ir_scope/ir_scope.c b/applications/external/ir_scope/ir_scope.c new file mode 100644 index 0000000000..179963a312 --- /dev/null +++ b/applications/external/ir_scope/ir_scope.c @@ -0,0 +1,208 @@ +// Author: github.com/kallanreed +#include +#include +#include +#include +#include +#include + +#define TAG "IR Scope" +#define COLS 128 +#define ROWS 8 + +typedef struct +{ + bool autoscale; + uint16_t us_per_sample; + size_t timings_cnt; + uint32_t* timings; + uint32_t timings_sum; + FuriMutex* mutex; +} IRScopeState; + +static void state_set_autoscale(IRScopeState* state) +{ + if (state->autoscale) + state->us_per_sample = state->timings_sum / (ROWS * COLS); +} + +static void canvas_draw_str_outline(Canvas* canvas, int x, int y, const char* str) +{ + canvas_set_color(canvas, ColorWhite); + for (int y1 = -1; y1 <= 1; ++y1) + for (int x1 = -1; x1 <= 1; ++x1) + canvas_draw_str(canvas, x + x1, y + y1, str); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_str(canvas, x, y, str); +} + +static void render_callback(Canvas* canvas, void* ctx) +{ + const IRScopeState* state = (IRScopeState*)ctx; + + furi_mutex_acquire(state->mutex, FuriWaitForever); + + canvas_clear(canvas); + canvas_draw_frame(canvas, 0, 0, 128, 64); + + // Draw the signal chart. + bool on = false; + bool done = false; + size_t ix = 0; + int timing_cols = -1; // Count of columns used to draw the current timing + for (size_t row = 0; row < ROWS && !done; ++row) + { + for (size_t col = 0; col < COLS && !done; ++col) + { + done = ix >= state->timings_cnt; + + if (!done && timing_cols < 0) + { + timing_cols = state->timings[ix] / state->us_per_sample; + on = !on; + } + + if (timing_cols == 0) ++ix; + + int y = row * 8 + 7; + canvas_draw_line(canvas, col, y, col, y - (on ? 5 : 0)); + --timing_cols; + } + } + + canvas_set_font(canvas, FontSecondary); + if (state->autoscale) + canvas_draw_str_outline(canvas, 100, 64, "Auto"); + else + { + char buf[20]; + snprintf(buf, sizeof(buf), "%uus", state->us_per_sample); + canvas_draw_str_outline(canvas, 100, 64, buf); + } + + furi_mutex_release(state->mutex); +} + +static void input_callback(InputEvent* input_event, void* ctx) +{ + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +static void ir_received_callback(void* ctx, InfraredWorkerSignal* signal) +{ + furi_check(signal); + IRScopeState* state = (IRScopeState*)ctx; + + furi_mutex_acquire(state->mutex, FuriWaitForever); + + const uint32_t* timings; + infrared_worker_get_raw_signal(signal, &timings, &state->timings_cnt); + + if (state->timings) + { + free(state->timings); + state->timings_sum = 0; + } + + state->timings = malloc(state->timings_cnt * sizeof(uint32_t)); + + // Copy and sum. + for (size_t i = 0; i < state->timings_cnt; ++i) + { + state->timings[i] = timings[i]; + state->timings_sum += timings[i]; + } + + state_set_autoscale(state); + + furi_mutex_release(state->mutex); +} + +int32_t ir_scope_app(void* p) +{ + UNUSED(p); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + furi_check(event_queue); + + if(furi_hal_infrared_is_busy()) + { + FURI_LOG_E(TAG, "Infrared is busy."); + return -1; + } + + IRScopeState state = { .autoscale = false, .us_per_sample = 200, + .timings = NULL, .timings_cnt = 0, .mutex = NULL }; + state.mutex = furi_mutex_alloc(FuriMutexTypeNormal); + if(!state.mutex) + { + FURI_LOG_E(TAG, "Cannot create mutex."); + return -1; + } + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &state); + view_port_input_callback_set(view_port, input_callback, event_queue); + + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + InfraredWorker* worker = infrared_worker_alloc(); + infrared_worker_rx_enable_signal_decoding(worker, false); + infrared_worker_rx_enable_blink_on_receiving(worker, true); + infrared_worker_rx_set_received_signal_callback(worker, ir_received_callback, &state); + infrared_worker_rx_start(worker); + + InputEvent event; + bool processing = true; + while(processing && furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) + { + if (event.type == InputTypeRelease) + { + furi_mutex_acquire(state.mutex, FuriWaitForever); + + if (event.key == InputKeyBack) + { + processing = false; + } + else if (event.key == InputKeyUp) + { + state.us_per_sample = MIN(1000, state.us_per_sample + 25); + state.autoscale = false; + } + else if (event.key == InputKeyDown) + { + state.us_per_sample = MAX(25, state.us_per_sample - 25); + state.autoscale = false; + } + else if (event.key == InputKeyOk) + { + state.autoscale = !state.autoscale; + if (state.autoscale) + state_set_autoscale(&state); + else + state.us_per_sample = 200; + } + + view_port_update(view_port); + furi_mutex_release(state.mutex); + } + } + + // Clean up. + infrared_worker_rx_stop(worker); + infrared_worker_free(worker); + + if (state.timings) free(state.timings); + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(state.mutex); + + return 0; +} diff --git a/applications/external/ir_scope/ir_scope.png b/applications/external/ir_scope/ir_scope.png new file mode 100644 index 0000000000000000000000000000000000000000..c0d7eaba0e2c1b18ed85bb9ec348ecbfb63a6575 GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ihk44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`0h7I;J!Gcbs$f-s|Jkje+3pq;0SV~9p@X|E$6g969lAODvNCJJxz;NQg8 z`;6IJfzu^AD}H}xN$}#DrmMn?wY|KxYc03`d0F11Iahh@4*6-C`yFp}^IHK;V(@hJ Kb6Mw<&;$VC0WZS< literal 0 HcmV?d00001 From f98ac4c48a267b0c536b76d8638432c8f7aeb677 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Mon, 3 Apr 2023 20:21:43 -0700 Subject: [PATCH 02/32] Add more detail to saved info screen (#2548) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add more detail to saved info screen * PR feedback * Format sources and add pvs temp files to gitignore Co-authored-by: あく --- .gitignore | 1 + .../scenes/picopass_scene_device_info.c | 54 ++++++++++++++----- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 45ac911394..81e985db7a 100644 --- a/.gitignore +++ b/.gitignore @@ -60,5 +60,6 @@ openocd.log # PVS Studio temporary files .PVS-Studio/ PVS-Studio.log +*.PVS-Studio.* .gdbinit diff --git a/applications/external/picopass/scenes/picopass_scene_device_info.c b/applications/external/picopass/scenes/picopass_scene_device_info.c index 046e9c8e45..41caeabf5a 100644 --- a/applications/external/picopass/scenes/picopass_scene_device_info.c +++ b/applications/external/picopass/scenes/picopass_scene_device_info.c @@ -14,43 +14,69 @@ void picopass_scene_device_info_widget_callback( void picopass_scene_device_info_on_enter(void* context) { Picopass* picopass = context; - FuriString* credential_str; - FuriString* wiegand_str; - credential_str = furi_string_alloc(); - wiegand_str = furi_string_alloc(); + FuriString* csn_str = furi_string_alloc_set("CSN:"); + FuriString* credential_str = furi_string_alloc(); + FuriString* wiegand_str = furi_string_alloc(); + FuriString* sio_str = furi_string_alloc(); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); // Setup view + PicopassBlock* AA1 = picopass->dev->dev_data.AA1; PicopassPacs* pacs = &picopass->dev->dev_data.pacs; Widget* widget = picopass->widget; - size_t bytesLength = 1 + pacs->record.bitLength / 8; - furi_string_set(credential_str, ""); - for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { - furi_string_cat_printf(credential_str, " %02X", pacs->credential[i]); + uint8_t csn[PICOPASS_BLOCK_LEN] = {0}; + memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN); + for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { + furi_string_cat_printf(csn_str, "%02X ", csn[i]); } - if(pacs->record.valid) { - furi_string_cat_printf( - wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); + if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { + // Neither of these are valid. Indicates the block was all 0x00 or all 0xff + furi_string_cat_printf(wiegand_str, "Invalid PACS"); } else { - furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); + size_t bytesLength = pacs->record.bitLength / 8; + if(pacs->record.bitLength % 8 > 0) { + // Add extra byte if there are bits remaining + bytesLength++; + } + furi_string_set(credential_str, ""); + for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { + furi_string_cat_printf(credential_str, " %02X", pacs->credential[i]); + } + + if(pacs->record.valid) { + furi_string_cat_printf( + wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); + } else { + furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); + } + + if(pacs->sio) { + furi_string_cat_printf(sio_str, "+SIO"); + } } widget_add_string_element( - widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); + widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); + widget_add_string_element( + widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); widget_add_string_element( widget, 64, - 32, + 36, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(credential_str)); + widget_add_string_element( + widget, 64, 46, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str)); + furi_string_free(csn_str); furi_string_free(credential_str); furi_string_free(wiegand_str); + furi_string_free(sio_str); widget_add_button_element( picopass->widget, From efc52ab46901b69f887cebbd9a9a8764b1cc11f7 Mon Sep 17 00:00:00 2001 From: Leo Smith <19672114+p4p1@users.noreply.github.com> Date: Tue, 4 Apr 2023 05:40:19 +0200 Subject: [PATCH 03/32] BdUSBadded WAIT_FOR_BUTTON_PRESS functionality (#2544) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: p4p1 Co-authored-by: あく --- .../main/bad_usb/helpers/ducky_script.c | 26 +++++++++++++++++++ .../main/bad_usb/helpers/ducky_script.h | 1 + .../bad_usb/helpers/ducky_script_commands.c | 12 +++++++++ .../main/bad_usb/helpers/ducky_script_i.h | 1 + .../main/bad_usb/views/bad_usb_view.c | 2 ++ .../file_formats/BadUsbScriptFormat.md | 7 +++++ 6 files changed, 49 insertions(+) diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 0206b7d09f..47d8a7e051 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -283,6 +283,10 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil delay_val = ducky_parse_line(bad_usb, bad_usb->line_prev); if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line return 0; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays + return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; } else if(delay_val < 0) { // Script error bad_usb->st.error_line = bad_usb->st.line_cur - 1; FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur - 1U); @@ -320,6 +324,8 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil return 0; } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; } else if(delay_val < 0) { bad_usb->st.error_line = bad_usb->st.line_cur; FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur); @@ -509,6 +515,9 @@ static int32_t bad_usb_worker(void* context) { delay_val = bad_usb->defdelay; bad_usb->string_print_pos = 0; worker_state = BadUsbStateStringDelay; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input + worker_state = BadUsbStateWaitForBtn; + bad_usb->st.state = BadUsbStateWaitForBtn; // Show long delays } else if(delay_val > 1000) { bad_usb->st.state = BadUsbStateDelay; // Show long delays bad_usb->st.delay_remain = delay_val / 1000; @@ -516,6 +525,23 @@ static int32_t bad_usb_worker(void* context) { } else { furi_check((flags & FuriFlagError) == 0); } + } else if(worker_state == BadUsbStateWaitForBtn) { // State: Wait for button Press + uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { + delay_val = 0; + worker_state = BadUsbStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadUsbStateNotConnected; // USB disconnected + furi_hal_hid_kb_release_all(); + } + bad_usb->st.state = worker_state; + continue; + } } else if(worker_state == BadUsbStateStringDelay) { // State: print string with delays uint32_t flags = furi_thread_flags_wait( WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, diff --git a/applications/main/bad_usb/helpers/ducky_script.h b/applications/main/bad_usb/helpers/ducky_script.h index 0e616242a8..cff7239420 100644 --- a/applications/main/bad_usb/helpers/ducky_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -15,6 +15,7 @@ typedef enum { BadUsbStateRunning, BadUsbStateDelay, BadUsbStateStringDelay, + BadUsbStateWaitForBtn, BadUsbStateDone, BadUsbStateScriptError, BadUsbStateFileError, diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c index f3de43c1bd..1498c9a73f 100644 --- a/applications/main/bad_usb/helpers/ducky_script_commands.c +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -143,6 +143,14 @@ static int32_t ducky_fnc_release(BadUsbScript* bad_usb, const char* line, int32_ return 0; } +static int32_t ducky_fnc_waitforbutton(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + UNUSED(bad_usb); + UNUSED(line); + + return SCRIPT_STATE_WAIT_FOR_BTN; +} + static const DuckyCmd ducky_commands[] = { {"REM ", NULL, -1}, {"ID ", NULL, -1}, @@ -160,8 +168,12 @@ static const DuckyCmd ducky_commands[] = { {"ALTCODE ", ducky_fnc_altstring, -1}, {"HOLD ", ducky_fnc_hold, -1}, {"RELEASE ", ducky_fnc_release, -1}, + {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, }; +#define TAG "BadUSB" +#define WORKER_TAG TAG "Worker" + int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line) { for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) { if(strncmp(line, ducky_commands[i].name, strlen(ducky_commands[i].name)) == 0) { diff --git a/applications/main/bad_usb/helpers/ducky_script_i.h b/applications/main/bad_usb/helpers/ducky_script_i.h index 0cda0fa2c2..84c7ef9de6 100644 --- a/applications/main/bad_usb/helpers/ducky_script_i.h +++ b/applications/main/bad_usb/helpers/ducky_script_i.h @@ -13,6 +13,7 @@ extern "C" { #define SCRIPT_STATE_NEXT_LINE (-3) #define SCRIPT_STATE_CMD_UNKNOWN (-4) #define SCRIPT_STATE_STRING_START (-5) +#define SCRIPT_STATE_WAIT_FOR_BTN (-6) #define FILE_BUFFER_LEN 16 diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 874d677c88..0ab4365b7b 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -51,6 +51,8 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { elements_button_left(canvas, "Config"); } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); + } else if(model->state.state == BadUsbStateWaitForBtn) { + elements_button_center(canvas, "Press to continue"); } else if(model->state.state == BadUsbStateWillRun) { elements_button_center(canvas, "Cancel"); } diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 8373bf6882..9cb848e2b7 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -77,6 +77,13 @@ Up to 5 keys can be hold simultaneously. | HOLD | Special key or single character | Press and hold key untill RELEASE command | | RELEASE | Special key or single character | Release key | +## Wait for button press + +Will wait indefinitely for a button to be pressed +| Command | Parameters | Notes | +| --------------------- | ------------ | --------------------------------------------------------------------- | +| WAIT_FOR_BUTTON_PRESS | None | Will wait for the user to press a button to continue script execution | + ## String From 494002505e38c50db1eda4b85702c4c6fadd6d30 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Tue, 4 Apr 2023 08:37:54 +0400 Subject: [PATCH 04/32] WS: fix protocol TX141TH-BV2 (#2559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../protocols/lacrosse_tx141thbv2.c | 145 ++++++++++-------- 1 file changed, 78 insertions(+), 67 deletions(-) diff --git a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c index 5d007b12fb..33a61cee06 100644 --- a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c +++ b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c @@ -2,11 +2,15 @@ #define TAG "WSProtocolLaCrosse_TX141THBv2" +#define LACROSSE_TX141TH_BV2_BIT_COUNT 41 + /* * Help * https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse_tx141x.c * - * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u + * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u - 41 bit + * or + * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | -40 bit * - i: identification; changes on battery switch * - c: lfsr_digest8_reflect; * - u: unknown; @@ -17,10 +21,10 @@ */ static const SubGhzBlockConst ws_protocol_lacrosse_tx141thbv2_const = { - .te_short = 250, - .te_long = 500, + .te_short = 208, + .te_long = 417, .te_delta = 120, - .min_count_bit_for_found = 41, + .min_count_bit_for_found = 40, }; struct WSProtocolDecoderLaCrosse_TX141THBv2 { @@ -102,14 +106,14 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context) { static bool ws_protocol_lacrosse_tx141thbv2_check_crc(WSProtocolDecoderLaCrosse_TX141THBv2* instance) { if(!instance->decoder.decode_data) return false; - uint8_t msg[] = { - instance->decoder.decode_data >> 33, - instance->decoder.decode_data >> 25, - instance->decoder.decode_data >> 17, - instance->decoder.decode_data >> 9}; + uint64_t data = instance->decoder.decode_data; + if(instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) { + data >>= 1; + } + uint8_t msg[] = {data >> 32, data >> 24, data >> 16, data >> 8}; uint8_t crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4); - return (crc == ((instance->decoder.decode_data >> 1) & 0xFF)); + return (crc == (data & 0xFF)); } /** @@ -117,14 +121,43 @@ static bool * @param instance Pointer to a WSBlockGeneric* instance */ static void ws_protocol_lacrosse_tx141thbv2_remote_controller(WSBlockGeneric* instance) { - instance->id = instance->data >> 33; - instance->battery_low = (instance->data >> 32) & 1; - instance->btn = (instance->data >> 31) & 1; - instance->channel = ((instance->data >> 29) & 0x03) + 1; - instance->temp = ((float)((instance->data >> 17) & 0x0FFF) - 500.0f) / 10.0f; - instance->humidity = (instance->data >> 9) & 0xFF; + uint64_t data = instance->data; + if(instance->data_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) { + data >>= 1; + } + instance->id = data >> 32; + instance->battery_low = (data >> 31) & 1; + instance->btn = (data >> 30) & 1; + instance->channel = ((data >> 28) & 0x03) + 1; + instance->temp = ((float)((data >> 16) & 0x0FFF) - 500.0f) / 10.0f; + instance->humidity = (data >> 8) & 0xFF; } +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static bool ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + WSProtocolDecoderLaCrosse_TX141THBv2* instance, + uint32_t te_last, + uint32_t te_current) { + furi_assert(instance); + bool ret = false; + if(DURATION_DIFF( + te_last + te_current, + ws_protocol_lacrosse_tx141thbv2_const.te_short + + ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) { + if(te_last > te_current) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + ret = true; + } + + return ret; +} void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration) { furi_assert(context); WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; @@ -132,7 +165,7 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin switch(instance->decoder.parser_step) { case LaCrosse_TX141THBv2DecoderStepReset: if((level) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; instance->decoder.te_last = duration; @@ -146,33 +179,17 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin } else { if((DURATION_DIFF( instance->decoder.te_last, - ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { //Found preambule instance->header_count++; } else if(instance->header_count == 4) { - if((DURATION_DIFF( - instance->decoder.te_last, - ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, - ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - subghz_protocol_blocks_add_bit(&instance->decoder, 1); + if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + instance, instance->decoder.te_last, duration)) { + instance->decoder.decode_data = instance->decoder.decode_data & 1; + instance->decoder.decode_count_bit = 1; instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; } else { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; @@ -198,37 +215,31 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) { + FURI_LOG_E( + "WS", + "%llX %d", + instance->decoder.decode_data, + instance->decoder.decode_count_bit); if((instance->decoder.decode_count_bit == - ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) && - ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_lacrosse_tx141thbv2_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); + ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) || + (instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT)) { + if(ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_lacrosse_tx141thbv2_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 1; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; + break; } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - instance->header_count = 1; - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; - break; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + instance, instance->decoder.te_last, duration)) { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; } else { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; From 862f89a57c47d64b90e85e7fbfd6c3b536d9f42f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 4 Apr 2023 13:32:05 +0300 Subject: [PATCH 05/32] Fix "Repeat" bugs --- applications/external/subbrute | 2 +- applications/main/subghz_remote/subghz_remote_app.c | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/applications/external/subbrute b/applications/external/subbrute index f9a4508e50..6f034d5ad3 160000 --- a/applications/external/subbrute +++ b/applications/external/subbrute @@ -1 +1 @@ -Subproject commit f9a4508e508393ce064179bc83b09e4516843400 +Subproject commit 6f034d5ad3c9292c7ac04da9e60a387a97040b89 diff --git a/applications/main/subghz_remote/subghz_remote_app.c b/applications/main/subghz_remote/subghz_remote_app.c index 991577cf57..7e3626b072 100644 --- a/applications/main/subghz_remote/subghz_remote_app.c +++ b/applications/main/subghz_remote/subghz_remote_app.c @@ -434,9 +434,6 @@ bool subghz_remote_save_protocol_to_file(FlipperFormat* fff_file, const char* de path_extract_dirname(dev_file_name, file_dir); do { - flipper_format_delete_key(fff_file, "Repeat"); - //flipper_format_delete_key(fff_file, "Manufacture"); - if(!storage_simply_mkdir(storage, furi_string_get_cstr(file_dir))) { FURI_LOG_E(TAG, "(save) Cannot mkdir"); break; @@ -474,14 +471,18 @@ void subghz_remote_tx_stop(SubGHzRemote* app) { //FURI_LOG_I(TAG, "TX Done!"); subghz_transmitter_stop(app->tx_transmitter); - FURI_LOG_D(TAG, "Checking if protocol is dynamic"); + //FURI_LOG_D(TAG, "Checking if protocol is dynamic"); const SubGhzProtocolRegistry* protocol_registry_items = subghz_environment_get_protocol_registry(app->environment); const SubGhzProtocol* proto = subghz_protocol_registry_get_by_name( protocol_registry_items, furi_string_get_cstr(app->txpreset->protocol)); - FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type); + //FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type); + + // Remove repeat if it was present + flipper_format_delete_key(app->tx_fff_data, "Repeat"); + if(proto && proto->type == SubGhzProtocolTypeDynamic) { - FURI_LOG_D(TAG, "Protocol is dynamic. Saving key"); + //FURI_LOG_D(TAG, "Protocol is dynamic. Saving key"); subghz_remote_save_protocol_to_file(app->tx_fff_data, app->tx_file_path); keeloq_reset_mfname(); From 2fe4508db891ec80de5257c29628acd47448f6f6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 4 Apr 2023 13:37:50 +0300 Subject: [PATCH 06/32] Fix repeats --- applications/external/subbrute | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external/subbrute b/applications/external/subbrute index 6f034d5ad3..df12fcd72e 160000 --- a/applications/external/subbrute +++ b/applications/external/subbrute @@ -1 +1 @@ -Subproject commit 6f034d5ad3c9292c7ac04da9e60a387a97040b89 +Subproject commit df12fcd72e168fd1b3007ca767fe8ba13c024ba7 From d4b32dc143f8149b564ce20fb770973d5f4f3e77 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 4 Apr 2023 13:50:52 +0300 Subject: [PATCH 07/32] Update submodule link --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index e6f3e7be1a..0442cb678b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -33,7 +33,7 @@ url = https://github.com/ataradov/free-dap.git [submodule "applications/external/subbrute"] path = applications/external/subbrute - url = https://github.com/derskythe/flipperzero-subbrute.git + url = https://github.com/DarkFlippers/flipperzero-subbrute.git [submodule "lib/heatshrink"] path = lib/heatshrink url = https://github.com/flipperdevices/heatshrink.git From 3b7b48be4aa3628254e3205e944981e8132af465 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 4 Apr 2023 17:38:33 +0300 Subject: [PATCH 08/32] Fix Repeat in subghz remote --- .../main/subghz_remote/subghz_remote_app.c | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/applications/main/subghz_remote/subghz_remote_app.c b/applications/main/subghz_remote/subghz_remote_app.c index 7e3626b072..9e4e23152e 100644 --- a/applications/main/subghz_remote/subghz_remote_app.c +++ b/applications/main/subghz_remote/subghz_remote_app.c @@ -385,17 +385,35 @@ bool subghz_remote_key_load( FURI_LOG_E(TAG, "Could not read Protocol."); break; } + if(!furi_string_cmp_str(preset->protocol, "RAW")) { subghz_protocol_raw_gen_fff_data(fff_data, path); + // repeat + if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) { + FURI_LOG_E(TAG, "Unable to insert or update Repeat"); + break; + } + if(!flipper_format_rewind(fff_data)) { + FURI_LOG_E(TAG, "Rewind error"); + return false; + } } else { stream_copy_full( flipper_format_get_raw_stream(fff_file), flipper_format_get_raw_stream(fff_data)); + // repeat + if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) { + FURI_LOG_E(TAG, "Unable to insert or update Repeat"); + break; + } + if(!flipper_format_rewind(fff_data)) { + FURI_LOG_E(TAG, "Rewind error"); + return false; + } } - // repeat - if(!flipper_format_insert_or_update_uint32(fff_file, "Repeat", &preset->repeat, 1)) { - FURI_LOG_E(TAG, "Unable to insert or update Repeat"); - break; + if(!flipper_format_rewind(fff_file)) { + FURI_LOG_E(TAG, "Rewind error"); + return false; } preset->decoder = subghz_receiver_search_decoder_base_by_name( @@ -478,11 +496,11 @@ void subghz_remote_tx_stop(SubGHzRemote* app) { protocol_registry_items, furi_string_get_cstr(app->txpreset->protocol)); //FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type); - // Remove repeat if it was present - flipper_format_delete_key(app->tx_fff_data, "Repeat"); - if(proto && proto->type == SubGhzProtocolTypeDynamic) { //FURI_LOG_D(TAG, "Protocol is dynamic. Saving key"); + // Remove repeat if it was present + flipper_format_delete_key(app->tx_fff_data, "Repeat"); + subghz_remote_save_protocol_to_file(app->tx_fff_data, app->tx_file_path); keeloq_reset_mfname(); From 61ec8818db8d9949b53981fad6bb398604f197f7 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 4 Apr 2023 21:59:05 +0300 Subject: [PATCH 09/32] fbt format --- applications/external/ir_scope/ir_scope.c | 101 ++++++++-------------- 1 file changed, 38 insertions(+), 63 deletions(-) diff --git a/applications/external/ir_scope/ir_scope.c b/applications/external/ir_scope/ir_scope.c index 179963a312..408ba3cc7e 100644 --- a/applications/external/ir_scope/ir_scope.c +++ b/applications/external/ir_scope/ir_scope.c @@ -10,35 +10,29 @@ #define COLS 128 #define ROWS 8 -typedef struct -{ - bool autoscale; - uint16_t us_per_sample; - size_t timings_cnt; - uint32_t* timings; - uint32_t timings_sum; - FuriMutex* mutex; +typedef struct { + bool autoscale; + uint16_t us_per_sample; + size_t timings_cnt; + uint32_t* timings; + uint32_t timings_sum; + FuriMutex* mutex; } IRScopeState; -static void state_set_autoscale(IRScopeState* state) -{ - if (state->autoscale) - state->us_per_sample = state->timings_sum / (ROWS * COLS); +static void state_set_autoscale(IRScopeState* state) { + if(state->autoscale) state->us_per_sample = state->timings_sum / (ROWS * COLS); } -static void canvas_draw_str_outline(Canvas* canvas, int x, int y, const char* str) -{ +static void canvas_draw_str_outline(Canvas* canvas, int x, int y, const char* str) { canvas_set_color(canvas, ColorWhite); - for (int y1 = -1; y1 <= 1; ++y1) - for (int x1 = -1; x1 <= 1; ++x1) - canvas_draw_str(canvas, x + x1, y + y1, str); + for(int y1 = -1; y1 <= 1; ++y1) + for(int x1 = -1; x1 <= 1; ++x1) canvas_draw_str(canvas, x + x1, y + y1, str); canvas_set_color(canvas, ColorBlack); canvas_draw_str(canvas, x, y, str); } -static void render_callback(Canvas* canvas, void* ctx) -{ +static void render_callback(Canvas* canvas, void* ctx) { const IRScopeState* state = (IRScopeState*)ctx; furi_mutex_acquire(state->mutex, FuriWaitForever); @@ -51,19 +45,16 @@ static void render_callback(Canvas* canvas, void* ctx) bool done = false; size_t ix = 0; int timing_cols = -1; // Count of columns used to draw the current timing - for (size_t row = 0; row < ROWS && !done; ++row) - { - for (size_t col = 0; col < COLS && !done; ++col) - { + for(size_t row = 0; row < ROWS && !done; ++row) { + for(size_t col = 0; col < COLS && !done; ++col) { done = ix >= state->timings_cnt; - if (!done && timing_cols < 0) - { + if(!done && timing_cols < 0) { timing_cols = state->timings[ix] / state->us_per_sample; on = !on; } - if (timing_cols == 0) ++ix; + if(timing_cols == 0) ++ix; int y = row * 8 + 7; canvas_draw_line(canvas, col, y, col, y - (on ? 5 : 0)); @@ -72,10 +63,9 @@ static void render_callback(Canvas* canvas, void* ctx) } canvas_set_font(canvas, FontSecondary); - if (state->autoscale) + if(state->autoscale) canvas_draw_str_outline(canvas, 100, 64, "Auto"); - else - { + else { char buf[20]; snprintf(buf, sizeof(buf), "%uus", state->us_per_sample); canvas_draw_str_outline(canvas, 100, 64, buf); @@ -84,14 +74,12 @@ static void render_callback(Canvas* canvas, void* ctx) furi_mutex_release(state->mutex); } -static void input_callback(InputEvent* input_event, void* ctx) -{ +static void input_callback(InputEvent* input_event, void* ctx) { FuriMessageQueue* event_queue = ctx; furi_message_queue_put(event_queue, input_event, FuriWaitForever); } -static void ir_received_callback(void* ctx, InfraredWorkerSignal* signal) -{ +static void ir_received_callback(void* ctx, InfraredWorkerSignal* signal) { furi_check(signal); IRScopeState* state = (IRScopeState*)ctx; @@ -100,8 +88,7 @@ static void ir_received_callback(void* ctx, InfraredWorkerSignal* signal) const uint32_t* timings; infrared_worker_get_raw_signal(signal, &timings, &state->timings_cnt); - if (state->timings) - { + if(state->timings) { free(state->timings); state->timings_sum = 0; } @@ -109,8 +96,7 @@ static void ir_received_callback(void* ctx, InfraredWorkerSignal* signal) state->timings = malloc(state->timings_cnt * sizeof(uint32_t)); // Copy and sum. - for (size_t i = 0; i < state->timings_cnt; ++i) - { + for(size_t i = 0; i < state->timings_cnt; ++i) { state->timings[i] = timings[i]; state->timings_sum += timings[i]; } @@ -120,24 +106,21 @@ static void ir_received_callback(void* ctx, InfraredWorkerSignal* signal) furi_mutex_release(state->mutex); } -int32_t ir_scope_app(void* p) -{ +int32_t ir_scope_app(void* p) { UNUSED(p); FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); furi_check(event_queue); - if(furi_hal_infrared_is_busy()) - { + if(furi_hal_infrared_is_busy()) { FURI_LOG_E(TAG, "Infrared is busy."); - return -1; + return -1; } - IRScopeState state = { .autoscale = false, .us_per_sample = 200, - .timings = NULL, .timings_cnt = 0, .mutex = NULL }; + IRScopeState state = { + .autoscale = false, .us_per_sample = 200, .timings = NULL, .timings_cnt = 0, .mutex = NULL}; state.mutex = furi_mutex_alloc(FuriMutexTypeNormal); - if(!state.mutex) - { + if(!state.mutex) { FURI_LOG_E(TAG, "Cannot create mutex."); return -1; } @@ -157,30 +140,22 @@ int32_t ir_scope_app(void* p) InputEvent event; bool processing = true; - while(processing && furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) - { - if (event.type == InputTypeRelease) - { + while(processing && + furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) { + if(event.type == InputTypeRelease) { furi_mutex_acquire(state.mutex, FuriWaitForever); - if (event.key == InputKeyBack) - { + if(event.key == InputKeyBack) { processing = false; - } - else if (event.key == InputKeyUp) - { + } else if(event.key == InputKeyUp) { state.us_per_sample = MIN(1000, state.us_per_sample + 25); state.autoscale = false; - } - else if (event.key == InputKeyDown) - { + } else if(event.key == InputKeyDown) { state.us_per_sample = MAX(25, state.us_per_sample - 25); state.autoscale = false; - } - else if (event.key == InputKeyOk) - { + } else if(event.key == InputKeyOk) { state.autoscale = !state.autoscale; - if (state.autoscale) + if(state.autoscale) state_set_autoscale(&state); else state.us_per_sample = 200; @@ -195,7 +170,7 @@ int32_t ir_scope_app(void* p) infrared_worker_rx_stop(worker); infrared_worker_free(worker); - if (state.timings) free(state.timings); + if(state.timings) free(state.timings); view_port_enabled_set(view_port, false); gui_remove_view_port(gui, view_port); From ef9dd5975b17ae05178b9e7709afb1ef2e1b6f41 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 4 Apr 2023 22:02:58 +0300 Subject: [PATCH 10/32] Update UART Terminal https://github.com/cool4uma/UART_Terminal/tree/main --- applications/external/uart_terminal/README.md | 9 ++- .../uart_terminal_scene_console_output.c | 20 ++++-- .../scenes/uart_terminal_scene_start.c | 1 + .../scenes/uart_terminal_scene_text_input.c | 8 ++- .../uart_terminal/uart_terminal_app_i.h | 3 +- .../external/uart_terminal/uart_text_input.c | 68 ++++++++++++++++--- 6 files changed, 89 insertions(+), 20 deletions(-) diff --git a/applications/external/uart_terminal/README.md b/applications/external/uart_terminal/README.md index 7e2cfd212d..b4a0db6cfd 100644 --- a/applications/external/uart_terminal/README.md +++ b/applications/external/uart_terminal/README.md @@ -1,5 +1,9 @@ -# UART Terminal for Flipper Zero +# UART Terminal for Flipper Zero [Flipper Zero](https://flipperzero.one/) app to control various devices via UART interface. +## Download fap +| **FW Official** | **FW Unleashed** | +| - | - | +| [![FAP Factory](https://flipc.org/api/v1/cool4uma/UART_Terminal/badge)](https://flipc.org/cool4uma/UART_Terminal) | [![FAP Factory](https://flipc.org/api/v1/cool4uma/UART_Terminal/badge?firmware=unleashed)](https://flipc.org/cool4uma/UART_Terminal?firmware=unleashed) | ## Capabilities - Read log and command output by uart @@ -29,6 +33,7 @@ Copy the contents of the repository to the applications_user/uart_terminal folde Or use the tool [uFBT](https://github.com/flipperdevices/flipperzero-ufbt) for building applications for Flipper Zero. + ## How it works @@ -42,4 +47,4 @@ Or use the tool [uFBT](https://github.com/flipperdevices/flipperzero-ufbt) for b ## INFO: -~70% of the source code is taken from the [Wifi Marauder](https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion) project. Many thanks to the developers of the Wifi Marauder project. +~60% of the source code is taken from the [Wifi Marauder](https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion) project. Many thanks to the developers of the Wifi Marauder project. diff --git a/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c b/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c index a9f998124f..38a5a20e4c 100644 --- a/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c +++ b/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c @@ -111,11 +111,21 @@ void uart_terminal_scene_console_output_on_enter(void* context) { uart_terminal_uart_set_handle_rx_data_cb( app->uart, uart_terminal_console_output_handle_rx_data_cb); // setup callback for rx thread - // Send command with newline '\n' + // Send command with CR+LF or newline '\n' if(app->is_command && app->selected_tx_string) { - uart_terminal_uart_tx( - (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string)); - uart_terminal_uart_tx((uint8_t*)("\n"), 1); + if(app->TERMINAL_MODE == 1) { + // char buffer[240]; + // snprintf(buffer, 240, "%s\r\n", (app->selected_tx_string)); + // uart_terminal_uart_tx((unsigned char *)buffer, strlen(buffer)); + uart_terminal_uart_tx( + (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string)); + uart_terminal_uart_tx((uint8_t*)("\r"), 1); + uart_terminal_uart_tx((uint8_t*)("\n"), 1); + } else { + uart_terminal_uart_tx( + (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string)); + uart_terminal_uart_tx((uint8_t*)("\n"), 1); + } } } @@ -144,4 +154,4 @@ void uart_terminal_scene_console_output_on_exit(void* context) { //if(app->is_command) { // uart_terminal_uart_tx((uint8_t*)("exit\n"), strlen("exit\n")); //} -} +} \ No newline at end of file diff --git a/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c b/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c index db783e9b2b..d4de748b63 100644 --- a/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c +++ b/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c @@ -31,6 +31,7 @@ const UART_TerminalItem items[NUM_MENU_ITEMS] = { FOCUS_CONSOLE_TOGGLE, NO_TIP}, {"Send command", {""}, 1, {""}, INPUT_ARGS, FOCUS_CONSOLE_END, NO_TIP}, + {"Send AT command", {""}, 1, {"AT"}, INPUT_ARGS, FOCUS_CONSOLE_END, NO_TIP}, {"Fast cmd", {"help", "uptime", "date", "df -h", "ps", "dmesg", "reboot", "poweroff"}, 8, diff --git a/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c b/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c index 62c0792d44..e200ba2661 100644 --- a/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c +++ b/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c @@ -25,7 +25,13 @@ void uart_terminal_scene_text_input_on_enter(void* context) { // Setup view UART_TextInput* text_input = app->text_input; // Add help message to header - uart_text_input_set_header_text(text_input, "Send command to UART"); + if(0 == strncmp("AT", app->selected_tx_string, strlen("AT"))) { + app->TERMINAL_MODE = 1; + uart_text_input_set_header_text(text_input, "Send AT command to UART"); + } else { + app->TERMINAL_MODE = 0; + uart_text_input_set_header_text(text_input, "Send command to UART"); + } uart_text_input_set_result_callback( text_input, uart_terminal_scene_text_input_callback, diff --git a/applications/external/uart_terminal/uart_terminal_app_i.h b/applications/external/uart_terminal/uart_terminal_app_i.h index 6b1996eb51..a0c4bb81f5 100644 --- a/applications/external/uart_terminal/uart_terminal_app_i.h +++ b/applications/external/uart_terminal/uart_terminal_app_i.h @@ -12,7 +12,7 @@ #include #include "uart_text_input.h" -#define NUM_MENU_ITEMS (4) +#define NUM_MENU_ITEMS (5) #define UART_TERMINAL_TEXT_BOX_STORE_SIZE (4096) #define UART_TERMINAL_TEXT_INPUT_STORE_SIZE (512) @@ -40,6 +40,7 @@ struct UART_TerminalApp { bool focus_console_start; bool show_stopscan_tip; int BAUDRATE; + int TERMINAL_MODE; //1=AT mode, 0=other mode }; typedef enum { diff --git a/applications/external/uart_terminal/uart_text_input.c b/applications/external/uart_terminal/uart_text_input.c index 4a571b127a..7d400b81ef 100644 --- a/applications/external/uart_terminal/uart_text_input.c +++ b/applications/external/uart_terminal/uart_text_input.c @@ -1,6 +1,7 @@ #include "uart_text_input.h" #include #include "uart_terminal_icons.h" +#include "uart_terminal_app_i.h" #include struct UART_TextInput { @@ -36,6 +37,8 @@ static const uint8_t keyboard_origin_x = 1; static const uint8_t keyboard_origin_y = 29; static const uint8_t keyboard_row_count = 4; +#define mode_AT "Send AT command to UART" + #define ENTER_KEY '\r' #define BACKSPACE_KEY '\b' @@ -163,6 +166,47 @@ static bool char_is_lowercase(char letter) { return (letter >= 0x61 && letter <= 0x7A); } +static bool char_is_uppercase(char letter) { + return (letter >= 0x41 && letter <= 0x5A); +} + +static char char_to_lowercase(const char letter) { + switch(letter) { + case ' ': + return 0x5f; + break; + case ')': + return 0x28; + break; + case '}': + return 0x7b; + break; + case ']': + return 0x5b; + break; + case '\\': + return 0x2f; + break; + case ':': + return 0x3b; + break; + case ',': + return 0x2e; + break; + case '?': + return 0x21; + break; + case '>': + return 0x3c; + break; + } + if(char_is_uppercase(letter)) { + return (letter + 0x20); + } else { + return letter; + } +} + static char char_to_uppercase(const char letter) { switch(letter) { case '_': @@ -193,7 +237,7 @@ static char char_to_uppercase(const char letter) { return 0x3e; break; } - if(isalpha(letter)) { + if(char_is_lowercase(letter)) { return (letter - 0x20); } else { return letter; @@ -209,7 +253,7 @@ static void uart_text_input_backspace_cb(UART_TextInputModel* model) { static void uart_text_input_view_draw_callback(Canvas* canvas, void* _model) { UART_TextInputModel* model = _model; - uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0; + //uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0; uint8_t needed_string_width = canvas_width(canvas) - 8; uint8_t start_pos = 4; @@ -291,15 +335,12 @@ static void uart_text_input_view_draw_callback(Canvas* canvas, void* _model) { } else { canvas_set_color(canvas, ColorBlack); } - - if(model->clear_default_text || - (text_length == 0 && char_is_lowercase(keys[column].text))) { + if(0 == strcmp(model->header, mode_AT)) { canvas_draw_glyph( canvas, keyboard_origin_x + keys[column].x, keyboard_origin_y + keys[column].y, - //char_to_uppercase(keys[column].text)); - keys[column].text); + char_to_uppercase(keys[column].text)); } else { canvas_draw_glyph( canvas, @@ -372,10 +413,18 @@ static void uart_text_input_handle_ok( char selected = get_selected_char(model); uint8_t text_length = strlen(model->text_buffer); - if(shift) { + if(0 == strcmp(model->header, mode_AT)) { selected = char_to_uppercase(selected); } + if(shift) { + if(0 == strcmp(model->header, mode_AT)) { + selected = char_to_lowercase(selected); + } else { + selected = char_to_uppercase(selected); + } + } + if(selected == ENTER_KEY) { if(model->validator_callback && (!model->validator_callback( @@ -392,9 +441,6 @@ static void uart_text_input_handle_ok( text_length = 0; } if(text_length < (model->text_buffer_size - 1)) { - if(text_length == 0 && char_is_lowercase(selected)) { - //selected = char_to_uppercase(selected); - } model->text_buffer[text_length] = selected; model->text_buffer[text_length + 1] = 0; } From 1923c159079a5bb6609be8ff82fcdea15a856ca1 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Tue, 4 Apr 2023 22:42:55 +0300 Subject: [PATCH 11/32] StarLine ignore --- .../main/subghz/helpers/subghz_types.h | 6 ++++ .../subghz/scenes/subghz_scene_receiver.c | 10 ++++++ .../scenes/subghz_scene_receiver_config.c | 32 +++++++++++++++++++ applications/main/subghz/subghz_i.h | 1 + 4 files changed, 49 insertions(+) diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 3c59824276..270fd7a210 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -35,6 +35,12 @@ typedef enum { SubGhzSpeakerStateEnable, } SubGhzSpeakerState; +/** SubGhzStarLineIgnore state */ +typedef enum { + SubGhzStarLineIgnoreDisable, + SubGhzStarLineIgnoreEnable, +} SubGhzStarLineIgnoreState; + /** SubGhzRxKeyState state */ typedef enum { SubGhzRxKeyStateIDLE, diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index c0112199ca..08fed3aa45 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -147,6 +147,16 @@ void subghz_scene_receiver_on_enter(void* context) { subghz_receiver_set_rx_callback( subghz->txrx->receiver, subghz_scene_add_to_history_callback, subghz); + if(subghz->txrx->starline_state == SubGhzStarLineIgnoreEnable) { + SubGhzProtocolDecoderBase* protocoldecoderbase = NULL; + protocoldecoderbase = + subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, "Star Line"); + if(protocoldecoderbase) { + subghz_protocol_decoder_base_set_decoder_callback( + protocoldecoderbase, NULL, subghz->txrx->receiver); + } + } + subghz->state_notifications = SubGhzNotificationStateRx; if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { subghz_rx_end(subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index ff51e2f4d0..89997b3142 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -68,6 +68,15 @@ const uint32_t bin_raw_value[BIN_RAW_COUNT] = { SubGhzProtocolFlag_Decodable, SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_BinRAW, }; +#define STAR_LINE_COUNT 2 +const char* const star_line_text[BIN_RAW_COUNT] = { + "OFF", + "ON", +}; +const uint32_t star_line_value[SPEAKER_COUNT] = { + SubGhzStarLineIgnoreDisable, + SubGhzStarLineIgnoreEnable, +}; uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) { furi_assert(context); @@ -217,6 +226,14 @@ static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* it subghz->txrx->raw_threshold_rssi = raw_threshold_rssi_value[index]; } +static void subghz_scene_receiver_config_set_starline(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, star_line_text[index]); + subghz->txrx->starline_state = star_line_value[index]; +} + static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { furi_assert(context); SubGhz* subghz = context; @@ -291,6 +308,21 @@ void subghz_scene_receiver_config_on_enter(void* context) { variable_item_set_current_value_text(item, bin_raw_text[value_index]); } + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != + SubGhzCustomEventManagerSet) { + item = variable_item_list_add( + subghz->variable_item_list, + "Ignore StarLine:", + STAR_LINE_COUNT, + subghz_scene_receiver_config_set_starline, + subghz); + + value_index = + value_index_uint32(subghz->txrx->starline_state, star_line_value, STAR_LINE_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, star_line_text[value_index]); + } + // Enable speaker, will send all incoming noises and signals to speaker so you can listen how your remote sounds like :) item = variable_item_list_add( subghz->variable_item_list, diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 393dd667d4..adabe4aad2 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -73,6 +73,7 @@ struct SubGhzTxRx { SubGhzTxRxState txrx_state; SubGhzHopperState hopper_state; SubGhzSpeakerState speaker_state; + SubGhzStarLineIgnoreState starline_state; uint8_t hopper_timeout; uint8_t hopper_idx_frequency; SubGhzRxKeyState rx_key_state; From 70eefe6de309bf82f61770b76299520bdf95c1a7 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Apr 2023 00:52:52 +0300 Subject: [PATCH 12/32] Fix --- .../main/subghz/scenes/subghz_scene_receiver_config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index 89997b3142..d22ed770b4 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -69,11 +69,11 @@ const uint32_t bin_raw_value[BIN_RAW_COUNT] = { SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_BinRAW, }; #define STAR_LINE_COUNT 2 -const char* const star_line_text[BIN_RAW_COUNT] = { +const char* const star_line_text[STAR_LINE_COUNT] = { "OFF", "ON", }; -const uint32_t star_line_value[SPEAKER_COUNT] = { +const uint32_t star_line_value[STAR_LINE_COUNT] = { SubGhzStarLineIgnoreDisable, SubGhzStarLineIgnoreEnable, }; From 4c488bd9704cb6af32bc9bcb523c3104c0a4a6a0 Mon Sep 17 00:00:00 2001 From: Anton Chistyakov Date: Wed, 5 Apr 2023 07:16:20 +0300 Subject: [PATCH 13/32] Fixing parsing troika number (#2536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/nfc/parsers/troika_4k_parser.c | 5 +++-- lib/nfc/parsers/troika_parser.c | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/nfc/parsers/troika_4k_parser.c b/lib/nfc/parsers/troika_4k_parser.c index 1f1b85a5c4..d248feb17c 100644 --- a/lib/nfc/parsers/troika_4k_parser.c +++ b/lib/nfc/parsers/troika_4k_parser.c @@ -90,13 +90,14 @@ bool troika_4k_parser_parse(NfcDeviceData* dev_data) { uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - temp_ptr = &data->block[8 * 4].value[3]; + temp_ptr = &data->block[8 * 4].value[2]; uint32_t number = 0; - for(size_t i = 0; i < 4; i++) { + for(size_t i = 1; i < 5; i++) { number <<= 8; number |= temp_ptr[i]; } number >>= 4; + number |= (temp_ptr[0] & 0xf) << 28; furi_string_printf( dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); diff --git a/lib/nfc/parsers/troika_parser.c b/lib/nfc/parsers/troika_parser.c index bfd22364b7..6d8ae98f3c 100644 --- a/lib/nfc/parsers/troika_parser.c +++ b/lib/nfc/parsers/troika_parser.c @@ -70,13 +70,14 @@ bool troika_parser_parse(NfcDeviceData* dev_data) { // Parse data uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - temp_ptr = &data->block[8 * 4].value[3]; + temp_ptr = &data->block[8 * 4].value[2]; uint32_t number = 0; - for(size_t i = 0; i < 4; i++) { + for(size_t i = 1; i < 5; i++) { number <<= 8; number |= temp_ptr[i]; } number >>= 4; + number |= (temp_ptr[0] & 0xf) << 28; furi_string_printf( dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); From 3cdb05364b9372e14e1bfd42d8f749f250b73d35 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Apr 2023 17:44:56 +0300 Subject: [PATCH 14/32] AnMotors AT4 - Add manually fixes Fix serial first two numbers to 47 - Just in case Looks like they are same for all remotes (may be incorrect) --- applications/main/subghz/scenes/subghz_scene_set_type.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index eb9e7d6201..431d5d2d73 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -558,7 +558,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { subghz, "AM650", 433920000, - (key & 0x00FFFFFF) | 0x04000000, + (key & 0x000FFFFF) | 0x04700000, 0x2, 0x0021, "AN-Motors")) { From 563ec6c2ab6f3ef92422d0cfdd6ec41165c528ac Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Apr 2023 22:45:27 +0300 Subject: [PATCH 15/32] Update TOTP https://github.com/akopachov/flipper-zero_authenticator --- applications/external/totp/application.fam | 5 +- applications/external/totp/cli/cli_helpers.c | 4 +- applications/external/totp/cli/cli_helpers.h | 25 +- .../external/totp/cli/commands/add/add.c | 64 +- .../totp/cli/commands/delete/delete.c | 2 +- .../totp/cli/commands/details/details.c | 2 +- .../external/totp/cli/commands/list/list.c | 2 +- .../external/totp/cli/commands/move/move.c | 2 +- .../external/totp/cli/commands/pin/pin.c | 3 +- .../totp/cli/commands/timezone/timezone.c | 5 +- .../totp/cli/commands/update/update.c | 73 +- .../totp/cli/common_command_arguments.c | 29 + .../totp/cli/common_command_arguments.h | 10 +- applications/external/totp/features_config.h | 14 +- .../external/totp/lib/base32/base32.c | 6 +- .../external/totp/lib/base32/base32.h | 5 +- .../external/totp/lib/base64/base64.c | 72 ++ .../external/totp/lib/base64/base64.h | 14 + .../list.c => linked_list/linked_list.c} | 2 +- .../list.h => linked_list/linked_list.h} | 24 +- .../external/totp/services/config/config.c | 8 +- .../external/totp/services/hmac/sha512.c | 5 +- .../external/totp/services/totp/totp.c | 18 +- .../external/totp/services/totp/totp.h | 4 +- .../external/totp/types/plugin_state.h | 2 +- applications/external/totp/types/token_info.c | 49 +- applications/external/totp/types/token_info.h | 26 +- .../external/totp/ui/fonts/font_info.h | 24 + .../totp/ui/fonts/mode-nine/mode_nine.c | 940 ++++++++++++++++++ .../totp/ui/fonts/mode-nine/mode_nine.h | 9 + .../add_new_token/totp_scene_add_new_token.c | 22 +- .../scenes/app_settings/totp_app_settings.c | 2 +- .../totp_scene_generate_token.c | 55 +- .../scenes/token_menu/totp_scene_token_menu.c | 4 +- .../totp/workers/bt_type_code/bt_type_code.c | 58 +- .../totp/workers/bt_type_code/bt_type_code.h | 11 + applications/external/totp/workers/common.c | 37 +- .../workers/usb_type_code/usb_type_code.c | 6 +- 38 files changed, 1450 insertions(+), 193 deletions(-) create mode 100644 applications/external/totp/lib/base64/base64.c create mode 100644 applications/external/totp/lib/base64/base64.h rename applications/external/totp/lib/{list/list.c => linked_list/linked_list.c} (99%) rename applications/external/totp/lib/{list/list.h => linked_list/linked_list.h} (79%) create mode 100644 applications/external/totp/ui/fonts/font_info.h create mode 100644 applications/external/totp/ui/fonts/mode-nine/mode_nine.c create mode 100644 applications/external/totp/ui/fonts/mode-nine/mode_nine.h diff --git a/applications/external/totp/application.fam b/applications/external/totp/application.fam index 3c7ba149ed..2c74457345 100644 --- a/applications/external/totp/application.fam +++ b/applications/external/totp/application.fam @@ -25,7 +25,10 @@ App( name="base32", ), Lib( - name="list", + name="base64", + ), + Lib( + name="linked_list" ), Lib( name="timezone_utils", diff --git a/applications/external/totp/cli/cli_helpers.c b/applications/external/totp/cli/cli_helpers.c index 9c524b4262..36b98cf650 100644 --- a/applications/external/totp/cli/cli_helpers.c +++ b/applications/external/totp/cli/cli_helpers.c @@ -41,7 +41,9 @@ bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input) { } else if(c == CliSymbolAsciiETX) { cli_nl(); return false; - } else if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + } else if( + (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + c == '/' || c == '=' || c == '+') { if(mask_user_input) { putc('*', stdout); } else { diff --git a/applications/external/totp/cli/cli_helpers.h b/applications/external/totp/cli/cli_helpers.h index f3f8d963d4..dd5a282d48 100644 --- a/applications/external/totp/cli/cli_helpers.h +++ b/applications/external/totp/cli/cli_helpers.h @@ -14,24 +14,13 @@ #define DOCOPT_OPTIONS "[options]" #define DOCOPT_DEFAULT(val) "[default: " val "]" -#define TOTP_CLI_PRINTF(format, ...) \ - do { \ - _Pragma(STRINGIFY(GCC diagnostic push)) \ - _Pragma(STRINGIFY(GCC diagnostic ignored "-Wdouble-promotion")) \ - printf(format, ##__VA_ARGS__); \ - _Pragma(STRINGIFY(GCC diagnostic pop)) \ - } while(false) - -#define TOTP_CLI_PRINTF_COLORFUL(color, format, ...) \ - do { \ - _Pragma(STRINGIFY(GCC diagnostic push)) \ - _Pragma(STRINGIFY(GCC diagnostic ignored "-Wdouble-promotion")) \ - printf("\e[%s", color); \ - printf(format, ##__VA_ARGS__); \ - printf("\e[0m"); \ - fflush(stdout); \ - _Pragma(STRINGIFY(GCC diagnostic pop)) \ - } while(false) +#define TOTP_CLI_PRINTF(format, ...) printf(format, ##__VA_ARGS__) + +#define TOTP_CLI_PRINTF_COLORFUL(color, format, ...) \ + printf("\e[%s", color); \ + printf(format, ##__VA_ARGS__); \ + printf("\e[0m"); \ + fflush(stdout) #define TOTP_CLI_COLOR_ERROR "91m" #define TOTP_CLI_COLOR_WARNING "93m" diff --git a/applications/external/totp/cli/commands/add/add.c b/applications/external/totp/cli/commands/add/add.c index 4cfd4fe06f..3549e785b5 100644 --- a/applications/external/totp/cli/commands/add/add.c +++ b/applications/external/totp/cli/commands/add/add.c @@ -1,7 +1,7 @@ #include "add.h" #include #include -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../../../services/config/config.h" #include "../../../services/convert/convert.h" @@ -17,11 +17,11 @@ void totp_cli_command_add_docopt_commands() { void totp_cli_command_add_docopt_usage() { TOTP_CLI_PRINTF( " " TOTP_CLI_COMMAND_NAME - " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL( + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING))) " " DOCOPT_OPTIONAL( DOCOPT_OPTION( - TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, + TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT( - TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n"); + TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n"); } void totp_cli_command_add_docopt_arguments() { @@ -35,11 +35,21 @@ void totp_cli_command_add_docopt_options() { TOTP_CLI_COMMAND_ARG_ALGO)) " Token hashing algorithm. Must be one of: " TOTP_TOKEN_ALGO_SHA1_NAME ", " TOTP_TOKEN_ALGO_SHA256_NAME ", " TOTP_TOKEN_ALGO_SHA512_NAME + ", " TOTP_TOKEN_ALGO_STEAM_NAME " " DOCOPT_DEFAULT(TOTP_TOKEN_ALGO_SHA1_NAME) "\r\n"); TOTP_CLI_PRINTF(" " DOCOPT_OPTION( TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT( - TOTP_CLI_COMMAND_ARG_DIGITS)) " Number of digits to generate, one of: 6, 8 " DOCOPT_DEFAULT("6") "\r\n"); + TOTP_CLI_COMMAND_ARG_DIGITS)) " Number of digits to generate, one of: 5, 6, 8 " DOCOPT_DEFAULT("6") "\r\n"); + + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ARG_SECRET_ENCODING)) " Token secret encoding, one of " PLAIN_TOKEN_ENCODING_BASE32_NAME + ", " PLAIN_TOKEN_ENCODING_BASE64_NAME + " " DOCOPT_DEFAULT( + PLAIN_TOKEN_ENCODING_BASE32_NAME) "\r\n"); + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT( @@ -83,13 +93,16 @@ void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cl // Read optional arguments bool mask_user_input = true; + PlainTokenSecretEncoding token_secret_encoding = PLAIN_TOKEN_ENCODING_BASE32; while(args_read_string_and_trim(args, temp_str)) { bool parsed = false; if(!totp_cli_try_read_algo(token_info, temp_str, args, &parsed) && !totp_cli_try_read_digits(token_info, temp_str, args, &parsed) && !totp_cli_try_read_duration(token_info, temp_str, args, &parsed) && !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) && - !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed)) { + !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed) && + !totp_cli_try_read_plain_token_secret_encoding( + temp_str, args, &parsed, &token_secret_encoding)) { totp_cli_printf_unknown_argument(temp_str); } @@ -115,31 +128,34 @@ void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cl TOTP_CLI_DELETE_LAST_LINE(); - if(!token_info_set_secret( - token_info, - furi_string_get_cstr(temp_str), - furi_string_size(temp_str), - plugin_state->iv)) { - TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n"); - furi_string_secure_free(temp_str); - token_info_free(token_info); - return; - } - - furi_string_secure_free(temp_str); - bool load_generate_token_scene = false; if(plugin_state->current_scene == TotpSceneGenerateToken) { totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); load_generate_token_scene = true; } - TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, token_info, furi_check); - plugin_state->tokens_count++; - if(totp_config_file_save_new_token(token_info) == TotpConfigFileUpdateSuccess) { - TOTP_CLI_PRINTF_SUCCESS("Token \"%s\" has been successfully added\r\n", token_info->name); + bool secret_set = token_info_set_secret( + token_info, + furi_string_get_cstr(temp_str), + furi_string_size(temp_str), + token_secret_encoding, + plugin_state->iv); + + furi_string_secure_free(temp_str); + + if(secret_set) { + TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, token_info, furi_check); + plugin_state->tokens_count++; + + if(totp_config_file_save_new_token(token_info) == TotpConfigFileUpdateSuccess) { + TOTP_CLI_PRINTF_SUCCESS( + "Token \"%s\" has been successfully added\r\n", token_info->name); + } else { + TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + } } else { - TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + token_info_free(token_info); + TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n"); } if(load_generate_token_scene) { diff --git a/applications/external/totp/cli/commands/delete/delete.c b/applications/external/totp/cli/commands/delete/delete.c index 96a205f577..a45525e4b0 100644 --- a/applications/external/totp/cli/commands/delete/delete.c +++ b/applications/external/totp/cli/commands/delete/delete.c @@ -3,7 +3,7 @@ #include #include #include -#include "../../../lib/list/list.h" +#include #include "../../../services/config/config.h" #include "../../cli_helpers.h" #include "../../../ui/scene_director.h" diff --git a/applications/external/totp/cli/commands/details/details.c b/applications/external/totp/cli/commands/details/details.c index aa16295a49..1b92894547 100644 --- a/applications/external/totp/cli/commands/details/details.c +++ b/applications/external/totp/cli/commands/details/details.c @@ -1,7 +1,7 @@ #include "details.h" #include #include -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../../../services/config/constants.h" #include "../../cli_helpers.h" diff --git a/applications/external/totp/cli/commands/list/list.c b/applications/external/totp/cli/commands/list/list.c index 6314b1b33b..951c102a0a 100644 --- a/applications/external/totp/cli/commands/list/list.c +++ b/applications/external/totp/cli/commands/list/list.c @@ -1,6 +1,6 @@ #include "list.h" #include -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../../../services/config/constants.h" #include "../../cli_helpers.h" diff --git a/applications/external/totp/cli/commands/move/move.c b/applications/external/totp/cli/commands/move/move.c index 265c7bc64c..5c4bdfcd62 100644 --- a/applications/external/totp/cli/commands/move/move.c +++ b/applications/external/totp/cli/commands/move/move.c @@ -2,7 +2,7 @@ #include #include -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../../../services/config/config.h" #include "../../cli_helpers.h" diff --git a/applications/external/totp/cli/commands/pin/pin.c b/applications/external/totp/cli/commands/pin/pin.c index b5b4943d6b..92c9c59c41 100644 --- a/applications/external/totp/cli/commands/pin/pin.c +++ b/applications/external/totp/cli/commands/pin/pin.c @@ -2,11 +2,12 @@ #include #include +#include #include "../../../types/token_info.h" #include "../../../types/user_pin_codes.h" #include "../../../services/config/config.h" #include "../../cli_helpers.h" -#include "../../../lib/polyfills/memset_s.h" +#include #include "../../../services/crypto/crypto.h" #include "../../../ui/scene_director.h" diff --git a/applications/external/totp/cli/commands/timezone/timezone.c b/applications/external/totp/cli/commands/timezone/timezone.c index 265d80e533..61e4fa0659 100644 --- a/applications/external/totp/cli/commands/timezone/timezone.c +++ b/applications/external/totp/cli/commands/timezone/timezone.c @@ -35,7 +35,7 @@ void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* arg if(*strtof_endptr == 0 && tz >= -12.75f && tz <= 12.75f) { plugin_state->timezone_offset = tz; if(totp_config_file_update_timezone_offset(tz) == TotpConfigFileUpdateSuccess) { - TOTP_CLI_PRINTF_SUCCESS("Timezone is set to %f\r\n", tz); + TOTP_CLI_PRINTF_SUCCESS("Timezone is set to %f\r\n", (double)tz); } else { TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); } @@ -50,7 +50,8 @@ void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* arg TOTP_CLI_PRINTF_ERROR("Invalid timezone offset\r\n"); } } else { - TOTP_CLI_PRINTF_INFO("Current timezone offset is %f\r\n", plugin_state->timezone_offset); + TOTP_CLI_PRINTF_INFO( + "Current timezone offset is %f\r\n", (double)plugin_state->timezone_offset); } furi_string_free(temp_str); } \ No newline at end of file diff --git a/applications/external/totp/cli/commands/update/update.c b/applications/external/totp/cli/commands/update/update.c index 669a36db89..bba7cad351 100644 --- a/applications/external/totp/cli/commands/update/update.c +++ b/applications/external/totp/cli/commands/update/update.c @@ -1,7 +1,7 @@ #include "update.h" #include #include -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../../../services/config/config.h" #include "../../../services/convert/convert.h" @@ -18,17 +18,20 @@ void totp_cli_command_update_docopt_commands() { void totp_cli_command_update_docopt_usage() { TOTP_CLI_PRINTF( " " TOTP_CLI_COMMAND_NAME - " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_UPDATE) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_NAME_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME))) " " DOCOPT_OPTIONAL( + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_UPDATE) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_NAME_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME))) " " DOCOPT_OPTIONAL( DOCOPT_OPTION( TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT( - TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n"); + TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n"); } void totp_cli_command_update_docopt_options() { TOTP_CLI_PRINTF(" " DOCOPT_OPTION( TOTP_CLI_COMMAND_ARG_NAME_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME)) " Token name\r\n"); + + TOTP_CLI_PRINTF(" " DOCOPT_SWITCH( + TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX) " Update token secret\r\n"); } static bool @@ -85,6 +88,7 @@ void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, // Read optional arguments bool mask_user_input = true; bool update_token_secret = false; + PlainTokenSecretEncoding token_secret_encoding = PLAIN_TOKEN_ENCODING_BASE32; while(args_read_string_and_trim(args, temp_str)) { bool parsed = false; if(!totp_cli_try_read_name(token_info, temp_str, args, &parsed) && @@ -93,7 +97,9 @@ void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, !totp_cli_try_read_duration(token_info, temp_str, args, &parsed) && !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) && !totp_cli_try_read_change_secret_flag(temp_str, &parsed, &update_token_secret) && - !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed)) { + !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed) && + !totp_cli_try_read_plain_token_secret_encoding( + temp_str, args, &parsed, &token_secret_encoding)) { totp_cli_printf_unknown_argument(temp_str); } @@ -105,54 +111,55 @@ void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, } } + bool token_secret_read = false; if(update_token_secret) { // Reading token secret furi_string_reset(temp_str); TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n"); - if(!totp_cli_read_line(cli, temp_str, mask_user_input) || - !totp_cli_ensure_authenticated(plugin_state, cli)) { - TOTP_CLI_DELETE_LAST_LINE(); + token_secret_read = totp_cli_read_line(cli, temp_str, mask_user_input); + TOTP_CLI_DELETE_LAST_LINE(); + if(!token_secret_read) { TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n"); - furi_string_secure_free(temp_str); - token_info_free(token_info); - return; } + } - TOTP_CLI_DELETE_LAST_LINE(); + bool load_generate_token_scene = false; + if(plugin_state->current_scene == TotpSceneGenerateToken) { + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + load_generate_token_scene = true; + } + bool token_secret_set = false; + if(update_token_secret && token_secret_read) { if(token_info->token != NULL) { free(token_info->token); } - - if(!token_info_set_secret( - token_info, - furi_string_get_cstr(temp_str), - furi_string_size(temp_str), - plugin_state->iv)) { + token_secret_set = token_info_set_secret( + token_info, + furi_string_get_cstr(temp_str), + furi_string_size(temp_str), + token_secret_encoding, + plugin_state->iv); + if(!token_secret_set) { TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n"); - furi_string_secure_free(temp_str); - token_info_free(token_info); - return; } } furi_string_secure_free(temp_str); - bool load_generate_token_scene = false; - if(plugin_state->current_scene == TotpSceneGenerateToken) { - totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); - load_generate_token_scene = true; - } - - list_item->data = token_info; + if(!update_token_secret || (token_secret_read && token_secret_set)) { + list_item->data = token_info; - if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) { - TOTP_CLI_PRINTF_SUCCESS( - "Token \"%s\" has been successfully updated\r\n", token_info->name); - token_info_free(existing_token_info); + if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) { + TOTP_CLI_PRINTF_SUCCESS( + "Token \"%s\" has been successfully updated\r\n", token_info->name); + token_info_free(existing_token_info); + } else { + TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + list_item->data = existing_token_info; + token_info_free(token_info); + } } else { - TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); - list_item->data = existing_token_info; token_info_free(token_info); } diff --git a/applications/external/totp/cli/common_command_arguments.c b/applications/external/totp/cli/common_command_arguments.c index 0ef121add9..9ed9f0126b 100644 --- a/applications/external/totp/cli/common_command_arguments.c +++ b/applications/external/totp/cli/common_command_arguments.c @@ -108,5 +108,34 @@ bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* return true; } + return false; +} + +bool totp_cli_try_read_plain_token_secret_encoding( + FuriString* arg, + FuriString* args, + bool* parsed, + PlainTokenSecretEncoding* secret_encoding) { + if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX) == 0) { + if(!args_read_string_and_trim(args, arg)) { + totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX); + } else { + if(furi_string_cmpi_str(arg, PLAIN_TOKEN_ENCODING_BASE32_NAME) == 0) { + *secret_encoding = PLAIN_TOKEN_ENCODING_BASE32; + *parsed = true; + } else if(furi_string_cmpi_str(arg, PLAIN_TOKEN_ENCODING_BASE64_NAME) == 0) { + *secret_encoding = PLAIN_TOKEN_ENCODING_BASE64; + *parsed = true; + } else { + TOTP_CLI_PRINTF_ERROR( + "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX + "\"\r\n", + furi_string_get_cstr(arg)); + } + } + + return true; + } + return false; } \ No newline at end of file diff --git a/applications/external/totp/cli/common_command_arguments.h b/applications/external/totp/cli/common_command_arguments.h index 85413a321d..be01c216d0 100644 --- a/applications/external/totp/cli/common_command_arguments.h +++ b/applications/external/totp/cli/common_command_arguments.h @@ -15,6 +15,8 @@ #define TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX "-b" #define TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE "feature" #define TOTP_CLI_COMMAND_ARG_INDEX "index" +#define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX "-e" +#define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING "encoding" void totp_cli_printf_unknown_argument(const FuriString* arg); void totp_cli_printf_missed_argument_value(char* arg); @@ -34,4 +36,10 @@ bool totp_cli_try_read_automation_features( FuriString* arg, FuriString* args, bool* parsed); -bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* unsecure_flag); \ No newline at end of file +bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* unsecure_flag); + +bool totp_cli_try_read_plain_token_secret_encoding( + FuriString* arg, + FuriString* args, + bool* parsed, + PlainTokenSecretEncoding* secret_encoding); \ No newline at end of file diff --git a/applications/external/totp/features_config.h b/applications/external/totp/features_config.h index d848b0acdf..845ec1d350 100644 --- a/applications/external/totp/features_config.h +++ b/applications/external/totp/features_config.h @@ -1,2 +1,14 @@ +// Include Bluetooth token input automation #define TOTP_BADBT_TYPE_ENABLED -#define TOTP_AUTOMATION_ICONS_ENABLED \ No newline at end of file + +// Include token input automation icons on the main screen +#define TOTP_AUTOMATION_ICONS_ENABLED + +// List of compatible firmwares +#define TOTP_FIRMWARE_OFFICIAL_STABLE 1 +#define TOTP_FIRMWARE_OFFICIAL_DEV 2 +#define TOTP_FIRMWARE_XTREME 3 +// End of list + +// Target firmware to build for +#define TOTP_TARGET_FIRMWARE TOTP_FIRMWARE_OFFICIAL_DEV \ No newline at end of file diff --git a/applications/external/totp/lib/base32/base32.c b/applications/external/totp/lib/base32/base32.c index 9781c831fd..827aa1e941 100644 --- a/applications/external/totp/lib/base32/base32.c +++ b/applications/external/totp/lib/base32/base32.c @@ -17,10 +17,10 @@ #include "base32.h" -int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize) { +size_t base32_decode(const uint8_t* encoded, uint8_t* result, size_t bufSize) { int buffer = 0; int bitsLeft = 0; - int count = 0; + size_t count = 0; for(const uint8_t* ptr = encoded; count < bufSize && *ptr; ++ptr) { uint8_t ch = *ptr; if(ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { @@ -43,7 +43,7 @@ int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize) { } else if(ch >= '2' && ch <= '7') { ch -= '2' - 26; } else { - return -1; + return 0; } buffer |= ch; diff --git a/applications/external/totp/lib/base32/base32.h b/applications/external/totp/lib/base32/base32.h index dea1a1c819..8cb9bddcb7 100644 --- a/applications/external/totp/lib/base32/base32.h +++ b/applications/external/totp/lib/base32/base32.h @@ -27,6 +27,7 @@ #pragma once +#include #include /** @@ -34,6 +35,6 @@ * @param encoded Base-32 encoded bytes * @param[out] result result output buffer * @param bufSize result output buffer size - * @return Decoded result length in bytes if successfully decoded; \c -1 otherwise + * @return Decoded result length in bytes if successfully decoded; \c 0 otherwise */ -int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize); +size_t base32_decode(const uint8_t* encoded, uint8_t* result, size_t bufSize); diff --git a/applications/external/totp/lib/base64/base64.c b/applications/external/totp/lib/base64/base64.c new file mode 100644 index 0000000000..1dfcf8814a --- /dev/null +++ b/applications/external/totp/lib/base64/base64.c @@ -0,0 +1,72 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + */ + +#include "base64.h" +#include + +static const uint8_t dtable[] = {0x3e, 0x80, 0x80, 0x80, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x80, 0x80, 0x80, 0x0, 0x80, + 0x80, 0x80, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33}; +// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static uint8_t get_dtable_value(uint8_t index) { + return (index < 43 || index > 122) ? 0x80 : dtable[index - 43]; +} + +uint8_t* base64_decode(const uint8_t* src, size_t len, size_t* out_len, size_t* out_size) { + uint8_t *out; + uint8_t *pos; + uint8_t in[4]; + uint8_t block[4]; + uint8_t tmp; + size_t i; + size_t count; + size_t olen; + + count = 0; + for(i = 0; i < len; i++) { + if(get_dtable_value(src[i]) != 0x80) count++; + } + + if(count == 0 || count % 4) return NULL; + olen = count / 4 * 3; + pos = out = malloc(olen); + *out_size = olen; + if(out == NULL) return NULL; + count = 0; + for(i = 0; i < len; i++) { + tmp = get_dtable_value(src[i]); + if(tmp == 0x80) continue; + in[count] = src[i]; + block[count] = tmp; + count++; + if(count == 4) { + *pos++ = (block[0] << 2) | (block[1] >> 4); + *pos++ = (block[1] << 4) | (block[2] >> 2); + *pos++ = (block[2] << 6) | block[3]; + count = 0; + } + } + if(pos > out) { + if(in[2] == '=') + pos -= 2; + else if(in[3] == '=') + pos--; + } + *out_len = pos - out; + return out; +} \ No newline at end of file diff --git a/applications/external/totp/lib/base64/base64.h b/applications/external/totp/lib/base64/base64.h new file mode 100644 index 0000000000..059ec5a909 --- /dev/null +++ b/applications/external/totp/lib/base64/base64.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +/** + * @brief Decodes Base-64 encoded bytes into plain bytes. + * @param src Base-64 encoded bytes + * @param len Base-64 encoded bytes count + * @param[out] out_len decoded buffer length + * @param[out] out_size decoded buffer allocated size + * @return Decoded result buffer if successfully decoded; \c NULL otherwise + */ +uint8_t* base64_decode(const uint8_t* src, size_t len, size_t* out_len, size_t* out_size); \ No newline at end of file diff --git a/applications/external/totp/lib/list/list.c b/applications/external/totp/lib/linked_list/linked_list.c similarity index 99% rename from applications/external/totp/lib/list/list.c rename to applications/external/totp/lib/linked_list/linked_list.c index f7abb6c8ef..23d10c548f 100644 --- a/applications/external/totp/lib/list/list.c +++ b/applications/external/totp/lib/linked_list/linked_list.c @@ -1,4 +1,4 @@ -#include "list.h" +#include "linked_list.h" ListNode* list_init_head(void* data) { ListNode* new = malloc(sizeof(ListNode)); diff --git a/applications/external/totp/lib/list/list.h b/applications/external/totp/lib/linked_list/linked_list.h similarity index 79% rename from applications/external/totp/lib/list/list.h rename to applications/external/totp/lib/linked_list/linked_list.h index c52d4c25a2..3c938e59a0 100644 --- a/applications/external/totp/lib/list/list.h +++ b/applications/external/totp/lib/linked_list/linked_list.h @@ -84,19 +84,15 @@ ListNode* list_insert_at(ListNode* head, uint16_t index, void* data); void list_free(ListNode* head); #define TOTP_LIST_INIT_OR_ADD(head, item, assert) \ - do { \ - if(head == NULL) { \ - head = list_init_head(item); \ - assert(head != NULL); \ - } else { \ - assert(list_add(head, item) != NULL); \ - } \ - } while(false) + if(head == NULL) { \ + head = list_init_head(item); \ + assert(head != NULL); \ + } else { \ + assert(list_add(head, item) != NULL); \ + } #define TOTP_LIST_FOREACH(head, node, action) \ - do { \ - ListNode* node = head; \ - while(node != NULL) { \ - action node = node->next; \ - } \ - } while(false) + ListNode* node = head; \ + while(node != NULL) { \ + action node = node->next; \ + } diff --git a/applications/external/totp/services/config/config.c b/applications/external/totp/services/config/config.c index 52065ad32d..adc85cbe59 100644 --- a/applications/external/totp/services/config/config.c +++ b/applications/external/totp/services/config/config.c @@ -1,7 +1,7 @@ #include "config.h" #include #include -#include "../list/list.h" +#include #include "../../types/common.h" #include "../../types/token_info.h" #include "../../features_config.h" @@ -139,10 +139,11 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF furi_string_printf( temp_str, - " # Token hashing algorithm to use during code generation. Supported options are %s, %s and %s. If you are not use which one to use - use %s", + " # Token hashing algorithm to use during code generation. Supported options are %s, %s, %s, and %s. If you are not use which one to use - use %s", TOTP_TOKEN_ALGO_SHA1_NAME, TOTP_TOKEN_ALGO_SHA256_NAME, TOTP_TOKEN_ALGO_SHA512_NAME, + TOTP_TOKEN_ALGO_STEAM_NAME, TOTP_TOKEN_ALGO_SHA1_NAME); flipper_format_write_comment(fff_data_file, temp_str); furi_string_printf( @@ -152,7 +153,7 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF flipper_format_write_comment_cstr( fff_data_file, - "# How many digits there should be in generated code. Available options are 6 and 8. Majority websites requires 6 digits code, however some rare websites wants to get 8 digits code. If you are not sure which one to use - use 6"); + "# How many digits there should be in generated code. Available options are 5, 6 and 8. Majority websites requires 6 digits code, however some rare websites wants to get 8 digits code. If you are not sure which one to use - use 6"); furi_string_printf(temp_str, "%s: 6", TOTP_CONFIG_KEY_TOKEN_DIGITS); flipper_format_write_comment(fff_data_file, temp_str); flipper_format_write_comment_cstr(fff_data_file, " "); @@ -686,6 +687,7 @@ TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state) tokenInfo, furi_string_get_cstr(temp_str), furi_string_size(temp_str), + PLAIN_TOKEN_ENCODING_BASE32, &plugin_state->iv[0])) { FURI_LOG_W(LOGGING_TAG, "Token \"%s\" has plain secret", tokenInfo->name); } else { diff --git a/applications/external/totp/services/hmac/sha512.c b/applications/external/totp/services/hmac/sha512.c index f04c4d593b..b56dd0f2eb 100644 --- a/applications/external/totp/services/hmac/sha512.c +++ b/applications/external/totp/services/hmac/sha512.c @@ -26,12 +26,9 @@ #include #include -#ifdef WORDS_BIGENDIAN -#define SWAP(n) (n) -#else #include "byteswap.h" + #define SWAP(n) swap_uint64(n) -#endif /* This array contains the bytes used to pad the buffer to the next 128-byte boundary. */ diff --git a/applications/external/totp/services/totp/totp.c b/applications/external/totp/services/totp/totp.c index 1a20d58c13..f6e0401e6a 100644 --- a/applications/external/totp/services/totp/totp.c +++ b/applications/external/totp/services/totp/totp.c @@ -11,7 +11,7 @@ #include "../hmac/byteswap.h" #include "../../lib/timezone_utils/timezone_utils.h" -#define HMAC_MAX_SIZE 64 +#define HMAC_MAX_RESULT_SIZE HMAC_SHA512_RESULT_SIZE /** * @brief Generates the timeblock for a time in seconds. @@ -29,19 +29,17 @@ uint64_t totp_timecode(uint8_t interval, uint64_t for_time) { /** * @brief Generates an OTP (One Time Password) * @param algo hashing algorithm to be used - * @param digits desired TOTP code length * @param plain_secret plain token secret * @param plain_secret_length plain token secret length * @param input input data for OTP code generation * @return OTP code if code was successfully generated; 0 otherwise */ -uint32_t otp_generate( +uint64_t otp_generate( TOTP_ALGO algo, - uint8_t digits, const uint8_t* plain_secret, size_t plain_secret_length, uint64_t input) { - uint8_t hmac[HMAC_MAX_SIZE] = {0}; + uint8_t hmac[HMAC_MAX_RESULT_SIZE] = {0}; uint64_t input_swapped = swap_uint64(input); @@ -55,14 +53,12 @@ uint32_t otp_generate( uint64_t i_code = ((hmac[offset] & 0x7F) << 24 | (hmac[offset + 1] & 0xFF) << 16 | (hmac[offset + 2] & 0xFF) << 8 | (hmac[offset + 3] & 0xFF)); - i_code %= (uint64_t)pow(10, digits); return i_code; } -uint32_t totp_at( +uint64_t totp_at( TOTP_ALGO algo, - uint8_t digits, const uint8_t* plain_secret, size_t plain_secret_length, uint64_t for_time, @@ -71,11 +67,7 @@ uint32_t totp_at( uint64_t for_time_adjusted = timezone_offset_apply(for_time, timezone_offset_from_hours(timezone)); return otp_generate( - algo, - digits, - plain_secret, - plain_secret_length, - totp_timecode(interval, for_time_adjusted)); + algo, plain_secret, plain_secret_length, totp_timecode(interval, for_time_adjusted)); } static int totp_algo_sha1( diff --git a/applications/external/totp/services/totp/totp.h b/applications/external/totp/services/totp/totp.h index 3f45a02237..d578f6ea99 100644 --- a/applications/external/totp/services/totp/totp.h +++ b/applications/external/totp/services/totp/totp.h @@ -39,7 +39,6 @@ extern const TOTP_ALGO TOTP_ALGO_SHA512; /** * @brief Generates a OTP key using the totp algorithm. * @param algo hashing algorithm to be used - * @param digits desired TOTP code length * @param plain_secret plain token secret * @param plain_secret_length plain token secret length * @param for_time the time the generated key will be created for @@ -47,9 +46,8 @@ extern const TOTP_ALGO TOTP_ALGO_SHA512; * @param interval token lifetime in seconds * @return TOTP code if code was successfully generated; 0 otherwise */ -uint32_t totp_at( +uint64_t totp_at( TOTP_ALGO algo, - uint8_t digits, const uint8_t* plain_secret, size_t plain_secret_length, uint64_t for_time, diff --git a/applications/external/totp/types/plugin_state.h b/applications/external/totp/types/plugin_state.h index 59a218ce3b..b1d34a662f 100644 --- a/applications/external/totp/types/plugin_state.h +++ b/applications/external/totp/types/plugin_state.h @@ -4,7 +4,7 @@ #include #include #include "../features_config.h" -#include "../lib/list/list.h" +#include #include "../ui/totp_scenes_enum.h" #include "notification_method.h" #include "automation_method.h" diff --git a/applications/external/totp/types/token_info.c b/applications/external/totp/types/token_info.c index fd9699ecf0..b8196c56b0 100644 --- a/applications/external/totp/types/token_info.c +++ b/applications/external/totp/types/token_info.c @@ -1,11 +1,11 @@ -#include #include "token_info.h" -#include "stdlib.h" +#include +#include +#include +#include +#include #include "common.h" -#include "../lib/base32/base32.h" #include "../services/crypto/crypto.h" -#include "../lib/polyfills/memset_s.h" -#include "../lib/polyfills/strnlen.h" TokenInfo* token_info_alloc() { TokenInfo* tokenInfo = malloc(sizeof(TokenInfo)); @@ -26,15 +26,32 @@ void token_info_free(TokenInfo* token_info) { bool token_info_set_secret( TokenInfo* token_info, - const char* base32_token_secret, + const char* plain_token_secret, size_t token_secret_length, + PlainTokenSecretEncoding plain_token_secret_encoding, const uint8_t* iv) { if(token_secret_length == 0) return false; + uint8_t* plain_secret; + size_t plain_secret_length; + size_t plain_secret_size; + if(plain_token_secret_encoding == PLAIN_TOKEN_ENCODING_BASE32) { + plain_secret_size = token_secret_length; + plain_secret = malloc(plain_secret_size); + furi_check(plain_secret != NULL); + plain_secret_length = + base32_decode((const uint8_t*)plain_token_secret, plain_secret, plain_secret_size); + } else if(plain_token_secret_encoding == PLAIN_TOKEN_ENCODING_BASE64) { + plain_secret_length = 0; + plain_secret = base64_decode( + (const uint8_t*)plain_token_secret, + token_secret_length, + &plain_secret_length, + &plain_secret_size); + furi_check(plain_secret != NULL); + } else { + return false; + } - uint8_t* plain_secret = malloc(token_secret_length); - furi_check(plain_secret != NULL); - int plain_secret_length = - base32_decode((const uint8_t*)base32_token_secret, plain_secret, token_secret_length); bool result; if(plain_secret_length > 0) { token_info->token = @@ -44,13 +61,16 @@ bool token_info_set_secret( result = false; } - memset_s(plain_secret, token_secret_length, 0, token_secret_length); + memset_s(plain_secret, plain_secret_size, 0, plain_secret_size); free(plain_secret); return result; } bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) { switch(digits) { + case 5: + token_info->digits = TOTP_5_DIGITS; + return true; case 6: token_info->digits = TOTP_6_DIGITS; return true; @@ -89,6 +109,11 @@ bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) return true; } + if(furi_string_cmpi_str(str, TOTP_TOKEN_ALGO_STEAM_NAME) == 0) { + token_info->algo = STEAM; + return true; + } + return false; } @@ -100,6 +125,8 @@ char* token_info_get_algo_as_cstr(const TokenInfo* token_info) { return TOTP_TOKEN_ALGO_SHA256_NAME; case SHA512: return TOTP_TOKEN_ALGO_SHA512_NAME; + case STEAM: + return TOTP_TOKEN_ALGO_STEAM_NAME; default: break; } diff --git a/applications/external/totp/types/token_info.h b/applications/external/totp/types/token_info.h index fea0c09d72..688e8028d8 100644 --- a/applications/external/totp/types/token_info.h +++ b/applications/external/totp/types/token_info.h @@ -7,10 +7,14 @@ #define TOTP_TOKEN_DURATION_DEFAULT 30 #define TOTP_TOKEN_ALGO_SHA1_NAME "sha1" +#define TOTP_TOKEN_ALGO_STEAM_NAME "steam" #define TOTP_TOKEN_ALGO_SHA256_NAME "sha256" #define TOTP_TOKEN_ALGO_SHA512_NAME "sha512" #define TOTP_TOKEN_MAX_LENGTH 255 +#define PLAIN_TOKEN_ENCODING_BASE32_NAME "base32" +#define PLAIN_TOKEN_ENCODING_BASE64_NAME "base64" + #define TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME "none" #define TOTP_TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME "enter" #define TOTP_TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME "tab" @@ -19,6 +23,7 @@ typedef uint8_t TokenHashAlgo; typedef uint8_t TokenDigitsCount; typedef uint8_t TokenAutomationFeature; +typedef uint8_t PlainTokenSecretEncoding; /** * @brief Hashing algorithm to be used to generate token @@ -37,13 +42,23 @@ enum TokenHashAlgos { /** * @brief SHA512 hashing algorithm */ - SHA512 + SHA512, + + /** + * @brief Algorithm used by Steam (Valve) + */ + STEAM }; /** * @brief Token digits count to be generated. */ enum TokenDigitsCounts { + /** + * @brief 6 digits + */ + TOTP_5_DIGITS = 5, + /** * @brief 6 digits */ @@ -80,6 +95,11 @@ enum TokenAutomationFeatures { TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER = 0b100 }; +enum PlainTokenSecretEncodings { + PLAIN_TOKEN_ENCODING_BASE32 = 0, + PLAIN_TOKEN_ENCODING_BASE64 = 1 +}; + #define TOTP_TOKEN_DIGITS_MAX_COUNT 8 /** @@ -139,13 +159,15 @@ void token_info_free(TokenInfo* token_info); * @param token_info instance where secret should be updated * @param base32_token_secret plain token secret in Base32 format * @param token_secret_length plain token secret length + * @param plain_token_secret_encoding plain token secret encoding * @param iv initialization vecor (IV) to be used for encryption * @return \c true if token successfully set; \c false otherwise */ bool token_info_set_secret( TokenInfo* token_info, - const char* base32_token_secret, + const char* plain_token_secret, size_t token_secret_length, + PlainTokenSecretEncoding plain_token_secret_encoding, const uint8_t* iv); /** diff --git a/applications/external/totp/ui/fonts/font_info.h b/applications/external/totp/ui/fonts/font_info.h new file mode 100644 index 0000000000..86c131ec9b --- /dev/null +++ b/applications/external/totp/ui/fonts/font_info.h @@ -0,0 +1,24 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include + +// This structure describes a single character's display information +typedef struct { + const uint8_t width; // width, in bits (or pixels), of the character + const uint16_t + offset; // offset of the character's bitmap, in bytes, into the the FONT_INFO's data array + +} FONT_CHAR_INFO; + +// Describes a single font +typedef struct { + const uint8_t height; // height, in pages (8 pixels), of the font's characters + const uint8_t startChar; // the first character in the font (e.g. in charInfo and data) + const uint8_t endChar; // the last character in the font + const uint8_t spacePixels; // number of pixels that a space character takes up + const FONT_CHAR_INFO* charInfo; // pointer to array of char information + const uint8_t* data; // pointer to generated array of character visual representation + +} FONT_INFO; \ No newline at end of file diff --git a/applications/external/totp/ui/fonts/mode-nine/mode_nine.c b/applications/external/totp/ui/fonts/mode-nine/mode_nine.c new file mode 100644 index 0000000000..add4f47ef6 --- /dev/null +++ b/applications/external/totp/ui/fonts/mode-nine/mode_nine.c @@ -0,0 +1,940 @@ +#include "mode_nine.h" + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for ModeNine 15pt +*/ + +/* Character bitmaps for ModeNine 15pt */ +const uint8_t modeNine_15ptBitmaps[] = { + /* @0 '-' (10 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @28 '0' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @56 '1' (10 pixels wide) */ + 0x30, + 0x00, + 0x38, + 0x00, + 0x3C, + 0x00, + 0x3C, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0xFC, + 0x00, + 0xFC, + 0x00, + + /* @84 '2' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x80, + 0x03, + 0xFC, + 0x01, + 0xFE, + 0x00, + 0x07, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + + /* @112 '3' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x80, + 0x03, + 0xC0, + 0x01, + 0xE0, + 0x00, + 0x70, + 0x00, + 0xF8, + 0x00, + 0xFC, + 0x01, + 0x80, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @140 '4' (10 pixels wide) */ + 0xE0, + 0x00, + 0xF0, + 0x00, + 0xF8, + 0x00, + 0xDC, + 0x00, + 0xCE, + 0x00, + 0xC7, + 0x00, + 0xC3, + 0x00, + 0xC3, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + + /* @168 '5' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x80, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @196 '6' (10 pixels wide) */ + 0xF0, + 0x00, + 0xFC, + 0x00, + 0x0E, + 0x00, + 0x06, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @224 '7' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x00, + 0x03, + 0x80, + 0x01, + 0xC0, + 0x01, + 0xE0, + 0x00, + 0x30, + 0x00, + 0x18, + 0x00, + 0x1C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + + /* @252 '8' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @280 '9' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x07, + 0x03, + 0xFE, + 0x03, + 0xFC, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x80, + 0x01, + 0xC0, + 0x01, + 0xFC, + 0x00, + 0x3C, + 0x00, + + /* @308 'B' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + + /* @336 'C' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @364 'D' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + + /* @392 'F' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + + /* @420 'G' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xC3, + 0x03, + 0xC3, + 0x03, + 0x03, + 0x03, + 0x07, + 0x03, + 0xFE, + 0x03, + 0xFC, + 0x03, + + /* @448 'H' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @476 'J' (10 pixels wide) */ + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @504 'K' (10 pixels wide) */ + 0x83, + 0x03, + 0xC3, + 0x01, + 0xE3, + 0x00, + 0x73, + 0x00, + 0x3B, + 0x00, + 0x1F, + 0x00, + 0x0F, + 0x00, + 0x0F, + 0x00, + 0x1F, + 0x00, + 0x3B, + 0x00, + 0x73, + 0x00, + 0xE3, + 0x00, + 0xC3, + 0x01, + 0x83, + 0x03, + + /* @532 'M' (10 pixels wide) */ + 0x03, + 0x03, + 0x87, + 0x03, + 0xCF, + 0x03, + 0xFF, + 0x03, + 0x7B, + 0x03, + 0x33, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @560 'N' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x07, + 0x03, + 0x0F, + 0x03, + 0x1F, + 0x03, + 0x3B, + 0x03, + 0x73, + 0x03, + 0xE3, + 0x03, + 0xC3, + 0x03, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @588 'P' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + + /* @616 'Q' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x33, + 0x03, + 0x73, + 0x03, + 0xE7, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x03, + + /* @644 'R' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + 0x1F, + 0x00, + 0x3B, + 0x00, + 0x73, + 0x00, + 0xE3, + 0x00, + 0xC3, + 0x01, + 0x83, + 0x03, + + /* @672 'T' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + + /* @700 'V' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x86, + 0x01, + 0x86, + 0x01, + 0xCC, + 0x00, + 0xCC, + 0x00, + 0x78, + 0x00, + 0x78, + 0x00, + 0x30, + 0x00, + + /* @728 'W' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0xFF, + 0x03, + 0xFE, + 0x01, + + /* @756 'X' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xCE, + 0x01, + 0xFC, + 0x00, + 0xFC, + 0x00, + 0xCE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @784 'Y' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xCE, + 0x01, + 0xFC, + 0x00, + 0x78, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, +}; + +/* Character descriptors for ModeNine 15pt */ +/* { [Char width in bits], [Offset into modeNine_15ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO modeNine_15ptDescriptors[] = { + {10, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {10, 28}, /* 0 */ + {10, 56}, /* 1 */ + {10, 84}, /* 2 */ + {10, 112}, /* 3 */ + {10, 140}, /* 4 */ + {10, 168}, /* 5 */ + {10, 196}, /* 6 */ + {10, 224}, /* 7 */ + {10, 252}, /* 8 */ + {10, 280}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {10, 308}, /* B */ + {10, 336}, /* C */ + {10, 364}, /* D */ + {0, 0}, /* E */ + {10, 392}, /* F */ + {10, 420}, /* G */ + {10, 448}, /* H */ + {0, 0}, /* I */ + {10, 476}, /* J */ + {10, 504}, /* K */ + {0, 0}, /* L */ + {10, 532}, /* M */ + {10, 560}, /* N */ + {0, 0}, /* O */ + {10, 588}, /* P */ + {10, 616}, /* Q */ + {10, 644}, /* R */ + {0, 0}, /* S */ + {10, 672}, /* T */ + {0, 0}, /* U */ + {10, 700}, /* V */ + {10, 728}, /* W */ + {10, 756}, /* X */ + {10, 784}, /* Y */ +}; + +/* Font information for ModeNine 15pt */ +const FONT_INFO modeNine_15ptFontInfo = { + 14, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 2, /* Width, in pixels, of space character */ + modeNine_15ptDescriptors, /* Character descriptor array */ + modeNine_15ptBitmaps, /* Character bitmap array */ +}; diff --git a/applications/external/totp/ui/fonts/mode-nine/mode_nine.h b/applications/external/totp/ui/fonts/mode-nine/mode_nine.h new file mode 100644 index 0000000000..67fa33afe5 --- /dev/null +++ b/applications/external/totp/ui/fonts/mode-nine/mode_nine.h @@ -0,0 +1,9 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" +#include + +/* Font data for ModeNine 15pt */ +extern const FONT_INFO modeNine_15ptFontInfo; diff --git a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c index f35f0238bc..800d7e672a 100644 --- a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c +++ b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c @@ -4,17 +4,17 @@ #include "../../scene_director.h" #include "totp_input_text.h" #include "../../../types/token_info.h" -#include "../../../lib/list/list.h" +#include #include "../../../services/config/config.h" #include "../../ui_controls.h" #include "../../common_dialogs.h" -#include "../../../lib/roll_value/roll_value.h" +#include #include "../../../types/nullable.h" #include "../generate_token/totp_scene_generate_token.h" -char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512"}; -char* TOKEN_DIGITS_TEXT_LIST[] = {"6 digits", "8 digits"}; -TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = {TOTP_6_DIGITS, TOTP_8_DIGITS}; +char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512", "Steam"}; +char* TOKEN_DIGITS_TEXT_LIST[] = {"5 digits", "6 digits", "8 digits"}; +TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = {TOTP_5_DIGITS, TOTP_6_DIGITS, TOTP_8_DIGITS}; typedef enum { TokenNameTextBox, @@ -95,6 +95,8 @@ void totp_scene_add_new_token_activate( scene_state->screen_y_offset = 0; + scene_state->digits_count_index = 1; + scene_state->input_state = NULL; scene_state->duration = TOTP_TOKEN_DURATION_DEFAULT; scene_state->duration_text = furi_string_alloc(); @@ -216,10 +218,10 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState break; case InputKeyRight: if(scene_state->selected_control == TokenAlgoSelect) { - totp_roll_value_uint8_t(&scene_state->algo, 1, SHA1, SHA512, RollOverflowBehaviorRoll); + totp_roll_value_uint8_t(&scene_state->algo, 1, SHA1, STEAM, RollOverflowBehaviorRoll); } else if(scene_state->selected_control == TokenLengthSelect) { totp_roll_value_uint8_t( - &scene_state->digits_count_index, 1, 0, 1, RollOverflowBehaviorRoll); + &scene_state->digits_count_index, 1, 0, 2, RollOverflowBehaviorRoll); } else if(scene_state->selected_control == TokenDurationSelect) { totp_roll_value_uint8_t(&scene_state->duration, 15, 15, 255, RollOverflowBehaviorStop); update_duration_text(scene_state); @@ -227,11 +229,10 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState break; case InputKeyLeft: if(scene_state->selected_control == TokenAlgoSelect) { - totp_roll_value_uint8_t( - &scene_state->algo, -1, SHA1, SHA512, RollOverflowBehaviorRoll); + totp_roll_value_uint8_t(&scene_state->algo, -1, SHA1, STEAM, RollOverflowBehaviorRoll); } else if(scene_state->selected_control == TokenLengthSelect) { totp_roll_value_uint8_t( - &scene_state->digits_count_index, -1, 0, 1, RollOverflowBehaviorRoll); + &scene_state->digits_count_index, -1, 0, 2, RollOverflowBehaviorRoll); } else if(scene_state->selected_control == TokenDurationSelect) { totp_roll_value_uint8_t( &scene_state->duration, -15, 15, 255, RollOverflowBehaviorStop); @@ -268,6 +269,7 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState tokenInfo, scene_state->token_secret, scene_state->token_secret_length, + PLAIN_TOKEN_ENCODING_BASE32, &plugin_state->iv[0]); if(token_secret_set) { diff --git a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c index 1671542b87..93fd3d9157 100644 --- a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c +++ b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c @@ -8,7 +8,7 @@ #include "../../constants.h" #include "../../../services/config/config.h" #include "../../../services/convert/convert.h" -#include "../../../lib/roll_value/roll_value.h" +#include #include "../../../types/nullable.h" #include "../../../features_config.h" #ifdef TOTP_BADBT_TYPE_ENABLED diff --git a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c index d3b27fdc70..f27b248357 100644 --- a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c +++ b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c @@ -19,7 +19,9 @@ #ifdef TOTP_BADBT_TYPE_ENABLED #include "../../../workers/bt_type_code/bt_type_code.h" #endif +#include "../../fonts/mode-nine/mode_nine.h" +static const char* STEAM_ALGO_ALPHABET = "23456789BCDFGHJKMNPQRTVWXY"; static const uint8_t PROGRESS_BAR_MARGIN = 3; static const uint8_t PROGRESS_BAR_HEIGHT = 4; @@ -121,13 +123,21 @@ static const NotificationSequence* return (NotificationSequence*)scene_state->notification_sequence_badusb; } -static void int_token_to_str(uint32_t i_token_code, char* str, TokenDigitsCount len) { +static void + int_token_to_str(uint64_t i_token_code, char* str, TokenDigitsCount len, TokenHashAlgo algo) { if(i_token_code == OTP_ERROR) { memset(&str[0], '-', len); } else { - for(int i = len - 1; i >= 0; i--) { - str[i] = CONVERT_DIGIT_TO_CHAR(i_token_code % 10); - i_token_code = i_token_code / 10; + if(algo == STEAM) { + for(uint8_t i = 0; i < len; i++) { + str[i] = STEAM_ALGO_ALPHABET[i_token_code % 26]; + i_token_code = i_token_code / 26; + } + } else { + for(int8_t i = len - 1; i >= 0; i--) { + str[i] = CONVERT_DIGIT_TO_CHAR(i_token_code % 10); + i_token_code = i_token_code / 10; + } } } @@ -137,6 +147,7 @@ static void int_token_to_str(uint32_t i_token_code, char* str, TokenDigitsCount static TOTP_ALGO get_totp_algo_impl(TokenHashAlgo algo) { switch(algo) { case SHA1: + case STEAM: return TOTP_ALGO_SHA1; case SHA256: return TOTP_ALGO_SHA256; @@ -161,6 +172,27 @@ static void update_totp_params(PluginState* const plugin_state) { } } +static void draw_totp_code(Canvas* const canvas, const SceneState* const scene_state) { + uint8_t code_length = scene_state->current_token->digits; + uint8_t char_width = modeNine_15ptFontInfo.charInfo[0].width; + uint8_t total_length = code_length * (char_width + modeNine_15ptFontInfo.spacePixels); + uint8_t offset_x = (SCREEN_WIDTH - total_length) >> 1; + uint8_t offset_y = SCREEN_HEIGHT_CENTER - (modeNine_15ptFontInfo.height >> 1); + for(uint8_t i = 0; i < code_length; i++) { + char ch = scene_state->last_code[i]; + uint8_t char_index = ch - modeNine_15ptFontInfo.startChar; + canvas_draw_xbm( + canvas, + offset_x, + offset_y, + char_width, + modeNine_15ptFontInfo.height, + &modeNine_15ptFontInfo.data[modeNine_15ptFontInfo.charInfo[char_index].offset]); + + offset_x += char_width + modeNine_15ptFontInfo.spacePixels; + } +} + void totp_scene_generate_token_init(const PluginState* plugin_state) { UNUSED(plugin_state); } @@ -274,19 +306,19 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_ int_token_to_str( totp_at( get_totp_algo_impl(tokenInfo->algo), - tokenInfo->digits, key, key_length, curr_ts, plugin_state->timezone_offset, tokenInfo->duration), scene_state->last_code, - tokenInfo->digits); + tokenInfo->digits, + tokenInfo->algo); memset_s(key, key_length, 0, key_length); free(key); } else { furi_mutex_acquire(scene_state->last_code_update_sync, FuriWaitForever); - int_token_to_str(0, scene_state->last_code, tokenInfo->digits); + int_token_to_str(0, scene_state->last_code, tokenInfo->digits, tokenInfo->algo); } furi_mutex_release(scene_state->last_code_update_sync); @@ -322,14 +354,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_ canvas_set_color(canvas, ColorBlack); } - canvas_set_font(canvas, FontBigNumbers); - canvas_draw_str_aligned( - canvas, - SCREEN_WIDTH_CENTER, - SCREEN_HEIGHT_CENTER, - AlignCenter, - AlignCenter, - scene_state->last_code); + draw_totp_code(canvas, scene_state); const uint8_t TOKEN_LIFETIME = scene_state->current_token->duration; float percentDone = (float)(TOKEN_LIFETIME - curr_ts % TOKEN_LIFETIME) / (float)TOKEN_LIFETIME; diff --git a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c index 1677626020..6c6986c659 100644 --- a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c +++ b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c @@ -6,13 +6,13 @@ #include "../../constants.h" #include "../../scene_director.h" #include "../../../services/config/config.h" -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../generate_token/totp_scene_generate_token.h" #include "../add_new_token/totp_scene_add_new_token.h" #include "../app_settings/totp_app_settings.h" #include "../../../types/nullable.h" -#include "../../../lib/roll_value/roll_value.h" +#include #define SCREEN_HEIGHT_THIRD (SCREEN_HEIGHT / 3) #define SCREEN_HEIGHT_THIRD_CENTER (SCREEN_HEIGHT_THIRD >> 1) diff --git a/applications/external/totp/workers/bt_type_code/bt_type_code.c b/applications/external/totp/workers/bt_type_code/bt_type_code.c index b84dfead92..ec4c1619da 100644 --- a/applications/external/totp/workers/bt_type_code/bt_type_code.c +++ b/applications/external/totp/workers/bt_type_code/bt_type_code.c @@ -1,5 +1,6 @@ #include "bt_type_code.h" #include +#include #include #include "../../types/common.h" #include "../../types/token_info.h" @@ -11,6 +12,26 @@ static inline bool totp_type_code_worker_stop_requested() { return furi_thread_flags_get() & TotpBtTypeCodeWorkerEventStop; } +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME +static void totp_type_code_worker_bt_set_app_mac(uint8_t* mac) { + uint8_t max_i; + size_t uid_size = furi_hal_version_uid_size(); + if(uid_size < 6) { + max_i = uid_size; + } else { + max_i = 6; + } + + const uint8_t* uid = furi_hal_version_uid(); + memcpy(mac, uid, max_i); + for(uint8_t i = max_i; i < 6; i++) { + mac[i] = 0; + } + + mac[0] = 0b10; +} +#endif + static void totp_type_code_worker_type_code(TotpBtTypeCodeWorkerContext* context) { uint8_t i = 0; do { @@ -30,7 +51,7 @@ static void totp_type_code_worker_type_code(TotpBtTypeCodeWorkerContext* context } static int32_t totp_type_code_worker_callback(void* context) { - furi_assert(context); + furi_check(context); FuriMutex* context_mutex = furi_mutex_alloc(FuriMutexTypeNormal); if(context_mutex == NULL) { return 251; @@ -74,7 +95,7 @@ void totp_bt_type_code_worker_start( char* code_buf, uint8_t code_buf_length, FuriMutex* code_buf_update_sync) { - furi_assert(context != NULL); + furi_check(context != NULL); context->string = code_buf; context->string_length = code_buf_length; context->string_sync = code_buf_update_sync; @@ -87,7 +108,7 @@ void totp_bt_type_code_worker_start( } void totp_bt_type_code_worker_stop(TotpBtTypeCodeWorkerContext* context) { - furi_assert(context != NULL); + furi_check(context != NULL); furi_thread_flags_set(furi_thread_get_id(context->thread), TotpBtTypeCodeWorkerEventStop); furi_thread_join(context->thread); furi_thread_free(context->thread); @@ -98,7 +119,7 @@ void totp_bt_type_code_worker_notify( TotpBtTypeCodeWorkerContext* context, TotpBtTypeCodeWorkerEvent event, uint8_t flags) { - furi_assert(context != NULL); + furi_check(context != NULL); context->flags = flags; furi_thread_flags_set(furi_thread_get_id(context->thread), event); } @@ -114,11 +135,33 @@ TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init() { furi_hal_bt_reinit(); furi_delay_ms(200); bt_keys_storage_set_storage_path(context->bt, HID_BT_KEYS_STORAGE_PATH); + +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME + totp_type_code_worker_bt_set_app_mac(&context->bt_mac[0]); + memcpy( + &context->previous_bt_name[0], + furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard), + TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN); + memcpy( + &context->previous_bt_mac[0], + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN); + char new_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN]; + snprintf(new_name, sizeof(new_name), "%s TOTP Auth", furi_hal_version_get_name_ptr()); + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, new_name); + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, context->bt_mac); +#endif + if(!bt_set_profile(context->bt, BtProfileHidKeyboard)) { FURI_LOG_E(LOGGING_TAG, "Failed to switch BT to keyboard HID profile"); } furi_hal_bt_start_advertising(); + +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME + bt_enable_peer_key_update(context->bt); +#endif + context->is_advertising = true; bt_set_status_changed_callback(context->bt, connection_status_changed_callback, context); @@ -126,7 +169,7 @@ TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init() { } void totp_bt_type_code_worker_free(TotpBtTypeCodeWorkerContext* context) { - furi_assert(context != NULL); + furi_check(context != NULL); if(context->thread != NULL) { totp_bt_type_code_worker_stop(context); @@ -142,6 +185,11 @@ void totp_bt_type_code_worker_free(TotpBtTypeCodeWorkerContext* context) { furi_delay_ms(200); bt_keys_storage_set_default_path(context->bt); +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, context->previous_bt_name); + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, context->previous_bt_mac); +#endif + if(!bt_set_profile(context->bt, BtProfileSerial)) { FURI_LOG_E(LOGGING_TAG, "Failed to switch BT to Serial profile"); } diff --git a/applications/external/totp/workers/bt_type_code/bt_type_code.h b/applications/external/totp/workers/bt_type_code/bt_type_code.h index 46fdb7aff8..edbe52e148 100644 --- a/applications/external/totp/workers/bt_type_code/bt_type_code.h +++ b/applications/external/totp/workers/bt_type_code/bt_type_code.h @@ -4,6 +4,12 @@ #include #include #include +#include "../../features_config.h" + +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME +#define TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN 18 +#define TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE +#endif typedef uint8_t TotpBtTypeCodeWorkerEvent; @@ -16,6 +22,11 @@ typedef struct { Bt* bt; bool is_advertising; bool is_connected; +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME + uint8_t bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN]; + char previous_bt_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN + 1]; + uint8_t previous_bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN]; +#endif } TotpBtTypeCodeWorkerContext; enum TotpBtTypeCodeWorkerEvents { diff --git a/applications/external/totp/workers/common.c b/applications/external/totp/workers/common.c index 5ca5d4fdf2..8ad0c2b46d 100644 --- a/applications/external/totp/workers/common.c +++ b/applications/external/totp/workers/common.c @@ -3,17 +3,15 @@ #include #include "../../services/convert/convert.h" -static const uint8_t hid_number_keys[10] = { - HID_KEYBOARD_0, - HID_KEYBOARD_1, - HID_KEYBOARD_2, - HID_KEYBOARD_3, - HID_KEYBOARD_4, - HID_KEYBOARD_5, - HID_KEYBOARD_6, - HID_KEYBOARD_7, - HID_KEYBOARD_8, - HID_KEYBOARD_9}; +static const uint8_t hid_number_keys[] = { + HID_KEYBOARD_0, HID_KEYBOARD_1, HID_KEYBOARD_2, HID_KEYBOARD_3, HID_KEYBOARD_4, + HID_KEYBOARD_5, HID_KEYBOARD_6, HID_KEYBOARD_7, HID_KEYBOARD_8, HID_KEYBOARD_9, + HID_KEYBOARD_A, HID_KEYBOARD_B, HID_KEYBOARD_C, HID_KEYBOARD_D, HID_KEYBOARD_E, + HID_KEYBOARD_F, HID_KEYBOARD_G, HID_KEYBOARD_H, HID_KEYBOARD_I, HID_KEYBOARD_J, + HID_KEYBOARD_K, HID_KEYBOARD_L, HID_KEYBOARD_M, HID_KEYBOARD_N, HID_KEYBOARD_O, + HID_KEYBOARD_P, HID_KEYBOARD_Q, HID_KEYBOARD_R, HID_KEYBOARD_S, HID_KEYBOARD_T, + HID_KEYBOARD_U, HID_KEYBOARD_V, HID_KEYBOARD_W, HID_KEYBOARD_X, HID_KEYBOARD_Y, + HID_KEYBOARD_Z}; static uint32_t get_keystroke_delay(TokenAutomationFeature features) { if(features & TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER) { @@ -49,10 +47,18 @@ void totp_type_code_worker_execute_automation( TokenAutomationFeature features) { furi_delay_ms(500); uint8_t i = 0; + totp_type_code_worker_press_key( + HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features); + while(i < string_length && string[i] != 0) { - uint8_t digit = CONVERT_CHAR_TO_DIGIT(string[i]); - if(digit > 9) break; - uint8_t hid_kb_key = hid_number_keys[digit]; + uint8_t char_index = CONVERT_CHAR_TO_DIGIT(string[i]); + if(char_index > 9) { + char_index = string[i] - 0x41 + 10; + } + + if(char_index > 35) break; + + uint8_t hid_kb_key = hid_number_keys[char_index]; totp_type_code_worker_press_key(hid_kb_key, key_press_fn, key_release_fn, features); furi_delay_ms(get_keystroke_delay(features)); i++; @@ -68,4 +74,7 @@ void totp_type_code_worker_execute_automation( furi_delay_ms(get_keystroke_delay(features)); totp_type_code_worker_press_key(HID_KEYBOARD_TAB, key_press_fn, key_release_fn, features); } + + totp_type_code_worker_press_key( + HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features); } \ No newline at end of file diff --git a/applications/external/totp/workers/usb_type_code/usb_type_code.c b/applications/external/totp/workers/usb_type_code/usb_type_code.c index d3e09a0657..5f7ccddf86 100644 --- a/applications/external/totp/workers/usb_type_code/usb_type_code.c +++ b/applications/external/totp/workers/usb_type_code/usb_type_code.c @@ -41,7 +41,7 @@ static void totp_type_code_worker_type_code(TotpUsbTypeCodeWorkerContext* contex } static int32_t totp_type_code_worker_callback(void* context) { - furi_assert(context); + furi_check(context); FuriMutex* context_mutex = furi_mutex_alloc(FuriMutexTypeNormal); if(context_mutex == NULL) { return 251; @@ -89,7 +89,7 @@ TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start( } void totp_usb_type_code_worker_stop(TotpUsbTypeCodeWorkerContext* context) { - furi_assert(context != NULL); + furi_check(context != NULL); furi_thread_flags_set(furi_thread_get_id(context->thread), TotpUsbTypeCodeWorkerEventStop); furi_thread_join(context->thread); furi_thread_free(context->thread); @@ -101,7 +101,7 @@ void totp_usb_type_code_worker_notify( TotpUsbTypeCodeWorkerContext* context, TotpUsbTypeCodeWorkerEvent event, uint8_t flags) { - furi_assert(context != NULL); + furi_check(context != NULL); context->flags = flags; furi_thread_flags_set(furi_thread_get_id(context->thread), event); } \ No newline at end of file From 9e74ca34428f26fdf0fc572e85d4ca5e9dfe2b26 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Apr 2023 23:51:20 +0300 Subject: [PATCH 16/32] Trying to fix telegram dev build message issues --- .drone.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.drone.yml b/.drone.yml index 58a1792535..11592861f2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -369,7 +369,7 @@ steps: commands: - curl -X POST -F 'key='$UPD_KEY'' $UPD_URL - - name: "Send files to telegram" + - name: "Send message to telegram" image: appleboy/drone-telegram settings: token: @@ -385,16 +385,24 @@ steps: Commit: https://github.com/DarkFlippers/unleashed-firmware/commit/{{commit.sha}} - Commit message: {{commit.message}} - [-> Sponsor our project](https://boosty.to/mmxdev) - [-Version with extra apps - Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-${DRONE_BUILD_NUMBER}e.tgz&channel=dev-cfw&version=${DRONE_BUILD_NUMBER}e) + [-Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-${DRONE_BUILD_NUMBER}.tgz&channel=dev-cfw&version=${DRONE_BUILD_NUMBER}) + + [-Version with extra apps - Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-${DRONE_BUILD_NUMBER}e.tgz&channel=dev-cfw&version=${DRONE_BUILD_NUMBER}e)" - [-Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-${DRONE_BUILD_NUMBER}.tgz&channel=dev-cfw&version=${DRONE_BUILD_NUMBER})" + - name: "Send build to telegram" + image: appleboy/drone-telegram + settings: + token: + from_secret: tgtoken + to: + from_secret: tgid_dev + format: markdown + message: "Regular Build:" document: - dev/flipper-z-f7-update-${DRONE_BUILD_NUMBER}.tgz From 2a26680acbfc2c526b48bac57b65132a0a1a94bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 6 Apr 2023 10:19:39 +0800 Subject: [PATCH 17/32] Furi: more gpio checks in HAL (#2549) * Furi: more gpio checks in HAL * Nfc: do not spawn service thread if it is already spawned Co-authored-by: Sergey Gavrilov --- firmware/targets/f7/furi_hal/furi_hal_gpio.c | 6 ++--- lib/ST25RFAL002/platform.c | 23 +++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_gpio.c b/firmware/targets/f7/furi_hal/furi_hal_gpio.c index e8318afcf5..afd482f2f5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_gpio.c +++ b/firmware/targets/f7/furi_hal/furi_hal_gpio.c @@ -53,8 +53,8 @@ void furi_hal_gpio_init( const GpioPull pull, const GpioSpeed speed) { // we cannot set alternate mode in this function - furi_assert(mode != GpioModeAltFunctionPushPull); - furi_assert(mode != GpioModeAltFunctionOpenDrain); + furi_check(mode != GpioModeAltFunctionPushPull); + furi_check(mode != GpioModeAltFunctionOpenDrain); furi_hal_gpio_init_ex(gpio, mode, pull, speed, GpioAltFnUnused); } @@ -178,7 +178,7 @@ void furi_hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, vo FURI_CRITICAL_ENTER(); uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); - furi_assert(gpio_interrupt[pin_num].callback == NULL); + furi_check(gpio_interrupt[pin_num].callback == NULL); gpio_interrupt[pin_num].callback = cb; gpio_interrupt[pin_num].context = ctx; gpio_interrupt[pin_num].ready = true; diff --git a/lib/ST25RFAL002/platform.c b/lib/ST25RFAL002/platform.c index 754e256501..5fe65ef880 100644 --- a/lib/ST25RFAL002/platform.c +++ b/lib/ST25RFAL002/platform.c @@ -45,16 +45,19 @@ void platformDisableIrqCallback() { void platformSetIrqCallback(PlatformIrqCallback callback) { rfal_platform.callback = callback; - rfal_platform.thread = - furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL); - furi_thread_mark_as_service(rfal_platform.thread); - furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr); - furi_thread_start(rfal_platform.thread); - - furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, nfc_isr, NULL); - // Disable interrupt callback as the pin is shared between 2 apps - // It is enabled in rfalLowPowerModeStop() - furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull); + + if(!rfal_platform.thread) { + rfal_platform.thread = + furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL); + furi_thread_mark_as_service(rfal_platform.thread); + furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr); + furi_thread_start(rfal_platform.thread); + + furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, nfc_isr, NULL); + // Disable interrupt callback as the pin is shared between 2 apps + // It is enabled in rfalLowPowerModeStop() + furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull); + } } bool platformSpiTxRx(const uint8_t* txBuf, uint8_t* rxBuf, uint16_t len) { From 8a021ae48c809abc554ad38e480b887760753eda Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 5 Apr 2023 19:26:33 -0700 Subject: [PATCH 18/32] [FL-3224] SD Driver: do not cache sd status. (#2560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SD Driver: do not cache sd status. * SD Driver: fix status getter --------- Co-authored-by: あく --- firmware/targets/f7/fatfs/user_diskio.c | 36 ++++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/firmware/targets/f7/fatfs/user_diskio.c b/firmware/targets/f7/fatfs/user_diskio.c index 74bf26f65d..6663d119cd 100644 --- a/firmware/targets/f7/fatfs/user_diskio.c +++ b/firmware/targets/f7/fatfs/user_diskio.c @@ -2,16 +2,14 @@ #include #include "sector_cache.h" -static volatile DSTATUS Stat = STA_NOINIT; - static DSTATUS driver_check_status(BYTE lun) { UNUSED(lun); - Stat = STA_NOINIT; - if(sd_get_card_state() == SdSpiStatusOK) { - Stat &= ~STA_NOINIT; + DSTATUS status = 0; + if(sd_get_card_state() != SdSpiStatusOK) { + status = STA_NOINIT; } - return Stat; + return status; } static DSTATUS driver_initialize(BYTE pdrv); @@ -107,6 +105,16 @@ static bool sd_device_write(uint32_t* buff, uint32_t sector, uint32_t count) { * @retval DSTATUS: Operation status */ static DSTATUS driver_initialize(BYTE pdrv) { + UNUSED(pdrv); + return RES_OK; +} + +/** + * @brief Gets Disk Status + * @param pdrv: Physical drive number (0..) + * @retval DSTATUS: Operation status + */ +static DSTATUS driver_status(BYTE pdrv) { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; @@ -118,16 +126,6 @@ static DSTATUS driver_initialize(BYTE pdrv) { return status; } -/** - * @brief Gets Disk Status - * @param pdrv: Physical drive number (0..) - * @retval DSTATUS: Operation status - */ -static DSTATUS driver_status(BYTE pdrv) { - UNUSED(pdrv); - return Stat; -} - /** * @brief Reads Sector(s) * @param pdrv: Physical drive number (0..) @@ -224,15 +222,15 @@ static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT coun * @retval DRESULT: Operation result */ static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff) { - UNUSED(pdrv); DRESULT res = RES_ERROR; SD_CardInfo CardInfo; - if(Stat & STA_NOINIT) return RES_NOTRDY; - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + DSTATUS status = driver_check_status(pdrv); + if(status & STA_NOINIT) return RES_NOTRDY; + switch(cmd) { /* Make sure that no pending write process */ case CTRL_SYNC: From a91d3198396bf9623d0b1181ef841811bf718c5f Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 6 Apr 2023 06:44:37 +0400 Subject: [PATCH 19/32] [FL-3162] Moved ufbt to fbt codebase (#2520) * scripts: moved ufbt code * ufbt: fixed tool path * ufbt: fixed linter/formatter target descriptions * scripts: ufbt: cleanup * fbt: moved fap launch target to tools; ufbt fixes * fbt: fixed missing headers from SDK * ufbt: removed debug output * ufbt: moved project template to main codebase * ufbt: fixed vscode_dist * ufbt: path naming changes * fbt: error message for older ufbt versions * ufbt: docs fixes * ufbt: fixed build dir location * fbt: fixes for extapps objcopy * fbt: extapps: removed extra debug output; fixed formatting * ufbt: handle launch target for multiple known apps * ufbt: dropping wrapper; linter fixes * ufbt: fixed boostrap path * ufbt: renamed entrypoint * ufbt: updated vscode config * ufbt: moved sconsign db location * ufbt: fixed sconsign path * fbt: SDK builders rework * fbt: reworked sdk packaging * ufbt: additional checks and state processing * ufbt: fixed sdk state file location * dist: not packaging pycache * dump commit json content * Github: more workflow debug prints * Github: fix incorrect commit meta extraction in get_env.py * ufbt, fbt: changed SConsEnvironmentError->StopError * fbtenv: no longer needs SCRIPT_PATH pre-set * ufbt: fixed sdk state check * scripts: exception fixes for storage.py * scripts: fbtenv: added FBT_TOOLCHAIN_PATH for on Windows for compat * ufbt: app template: creating .gitkeep for images folder * ufbt: app template: fixed .gitkeep creation * docs: formatting fixes for AppManifests; added link to ufbt * fbt: added link to PyPI for old ufbt versions * sdk: fixed dir component paths Co-authored-by: Aleksandr Kutuzov --- .../loader/firmware_api/firmware_api.cpp | 2 +- documentation/AppManifests.md | 12 +- documentation/fbt.md | 2 + firmware.scons | 4 +- scripts/fbt_tools/fbt_assets.py | 4 +- scripts/fbt_tools/fbt_extapps.py | 78 +++- scripts/fbt_tools/fbt_sdk.py | 54 +-- scripts/fbt_tools/fbt_tweaks.py | 7 + scripts/flipper/storage.py | 18 +- scripts/get_env.py | 11 +- scripts/sconsdist.py | 212 +++++++--- scripts/serial_cli.py | 2 +- scripts/toolchain/fbtenv.cmd | 6 +- scripts/toolchain/fbtenv.sh | 34 +- scripts/ufbt/SConstruct | 393 ++++++++++++++++++ scripts/ufbt/commandline.scons | 90 ++++ scripts/ufbt/project_template/.clang-format | 191 +++++++++ scripts/ufbt/project_template/.editorconfig | 13 + scripts/ufbt/project_template/.gitignore | 4 + .../.vscode/c_cpp_properties.json | 14 + .../project_template/.vscode/extensions.json | 18 + .../ufbt/project_template/.vscode/launch.json | 98 +++++ .../project_template/.vscode/settings.json | 20 + .../ufbt/project_template/.vscode/tasks.json | 54 +++ .../app_template/${FBT_APPID}.c | 12 + .../app_template/${FBT_APPID}.png | Bin 0 -> 220 bytes .../app_template/application.fam | 17 + scripts/ufbt/site_init.py | 36 ++ scripts/ufbt/site_tools/ufbt_help.py | 53 +++ scripts/ufbt/site_tools/ufbt_state.py | 117 ++++++ scripts/ufbt/update.scons | 37 ++ site_scons/extapps.scons | 81 +--- 32 files changed, 1495 insertions(+), 199 deletions(-) create mode 100644 scripts/ufbt/SConstruct create mode 100644 scripts/ufbt/commandline.scons create mode 100644 scripts/ufbt/project_template/.clang-format create mode 100644 scripts/ufbt/project_template/.editorconfig create mode 100644 scripts/ufbt/project_template/.gitignore create mode 100644 scripts/ufbt/project_template/.vscode/c_cpp_properties.json create mode 100644 scripts/ufbt/project_template/.vscode/extensions.json create mode 100644 scripts/ufbt/project_template/.vscode/launch.json create mode 100644 scripts/ufbt/project_template/.vscode/settings.json create mode 100644 scripts/ufbt/project_template/.vscode/tasks.json create mode 100644 scripts/ufbt/project_template/app_template/${FBT_APPID}.c create mode 100644 scripts/ufbt/project_template/app_template/${FBT_APPID}.png create mode 100644 scripts/ufbt/project_template/app_template/application.fam create mode 100644 scripts/ufbt/site_init.py create mode 100644 scripts/ufbt/site_tools/ufbt_help.py create mode 100644 scripts/ufbt/site_tools/ufbt_state.py create mode 100644 scripts/ufbt/update.scons diff --git a/applications/services/loader/firmware_api/firmware_api.cpp b/applications/services/loader/firmware_api/firmware_api.cpp index 814dd82c92..52e86efc26 100644 --- a/applications/services/loader/firmware_api/firmware_api.cpp +++ b/applications/services/loader/firmware_api/firmware_api.cpp @@ -4,7 +4,7 @@ #include /* Generated table */ -#include +#include static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 195fe92568..99f6386b2c 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -75,12 +75,12 @@ Example for building an app from Rust sources: Library sources must be placed in a subfolder of the `lib` folder within the application's source folder. Each library is defined as a call to the `Lib()` function, accepting the following parameters: - - **name**: name of the library's folder. Required. - - **fap_include_paths**: list of the library's relative paths to add to the parent fap's include path list. The default value is `["."]`, meaning the library's source root. - - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. - - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. - - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. - - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. + - **name**: name of the library's folder. Required. + - **fap_include_paths**: list of the library's relative paths to add to the parent fap's include path list. The default value is `["."]`, meaning the library's source root. + - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. + - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. + - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. + - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. Example for building an app with a private library: diff --git a/documentation/fbt.md b/documentation/fbt.md index 65729c5c84..14d63e9cea 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -3,6 +3,8 @@ FBT is the entry point for firmware-related commands and utilities. It is invoked by `./fbt` in the firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. +If you don't need all features of `fbt` - like building the whole firmware - and only want to build and debug a single application, you can use [ufbt](https://pypi.org/project/ufbt/). + ## Environment To use `fbt`, you only need `git` installed in your system. diff --git a/firmware.scons b/firmware.scons index a094765af7..c7fdc63925 100644 --- a/firmware.scons +++ b/firmware.scons @@ -68,7 +68,7 @@ env = ENV.Clone( ], }, }, - SDK_APISYMS=None, + FW_API_TABLE=None, _APP_ICONS=None, ) @@ -241,7 +241,7 @@ Depends( [ fwenv["FW_VERSION_JSON"], fwenv["FW_ASSETS_HEADERS"], - fwenv["SDK_APISYMS"], + fwenv["FW_API_TABLE"], fwenv["_APP_ICONS"], ], ) diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index d2a58f3fb8..e4c567993e 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -1,6 +1,6 @@ from SCons.Builder import Builder from SCons.Action import Action -from SCons.Errors import SConsEnvironmentError +from SCons.Errors import StopError import os import subprocess @@ -90,7 +90,7 @@ def proto_ver_generator(target, source, env): source_dir=src_dir, ) except (subprocess.CalledProcessError, EnvironmentError) as e: - raise SConsEnvironmentError("Git: describe failed") + raise StopError("Git: describe failed") git_major, git_minor = git_describe.split(".") version_file_data = ( diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index ef0c2d3012..4ac1c68737 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -21,6 +21,10 @@ from fbt.util import extract_abs_dir_path +_FAP_META_SECTION = ".fapmeta" +_FAP_FILEASSETS_SECTION = ".fapassets" + + @dataclass class FlipperExternalAppInfo: app: FlipperApplication @@ -234,6 +238,8 @@ def BuildAppElf(env, app): def prepare_app_metadata(target, source, env): + metadata_node = next(filter(lambda t: t.name.endswith(_FAP_META_SECTION), target)) + sdk_cache = SdkCache(env["SDK_DEFINITION"].path, load_version_only=True) if not sdk_cache.is_buildable(): @@ -242,8 +248,7 @@ def prepare_app_metadata(target, source, env): ) app = env["APP"] - meta_file_name = source[0].path + ".meta" - with open(meta_file_name, "wb") as f: + with open(metadata_node.abspath, "wb") as f: f.write( assemble_manifest_data( app_manifest=app, @@ -337,24 +342,26 @@ def embed_app_metadata_emitter(target, source, env): if app.apptype == FlipperAppType.PLUGIN: target[0].name = target[0].name.replace(".fap", ".fal") - meta_file_name = source[0].path + ".meta" - target.append("#" + meta_file_name) + target.append(env.File(source[0].abspath + _FAP_META_SECTION)) if app.fap_file_assets: - files_section = source[0].path + ".files.section" - target.append("#" + files_section) + target.append(env.File(source[0].abspath + _FAP_FILEASSETS_SECTION)) return (target, source) def prepare_app_files(target, source, env): + files_section_node = next( + filter(lambda t: t.name.endswith(_FAP_FILEASSETS_SECTION), target) + ) + app = env["APP"] - directory = app._appdir.Dir(app.fap_file_assets) + directory = env.Dir(app._apppath).Dir(app.fap_file_assets) if not directory.exists(): raise UserError(f"File asset directory {directory} does not exist") bundler = FileBundler(directory.abspath) - bundler.export(source[0].path + ".files.section") + bundler.export(files_section_node.abspath) def generate_embed_app_metadata_actions(source, target, env, for_signature): @@ -367,15 +374,15 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): objcopy_str = ( "${OBJCOPY} " "--remove-section .ARM.attributes " - "--add-section .fapmeta=${SOURCE}.meta " + "--add-section ${_FAP_META_SECTION}=${SOURCE}${_FAP_META_SECTION} " ) if app.fap_file_assets: actions.append(Action(prepare_app_files, "$APPFILE_COMSTR")) - objcopy_str += "--add-section .fapassets=${SOURCE}.files.section " + objcopy_str += "--add-section ${_FAP_FILEASSETS_SECTION}=${SOURCE}${_FAP_FILEASSETS_SECTION} " objcopy_str += ( - "--set-section-flags .fapmeta=contents,noload,readonly,data " + "--set-section-flags ${_FAP_META_SECTION}=contents,noload,readonly,data " "--strip-debug --strip-unneeded " "--add-gnu-debuglink=${SOURCE} " "${SOURCES} ${TARGET}" @@ -391,6 +398,51 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): return Action(actions) +def AddAppLaunchTarget(env, appname, launch_target_name): + deploy_sources, flipp_dist_paths, validators = [], [], [] + run_script_extra_ars = "" + + def _add_dist_targets(app_artifacts): + validators.append(app_artifacts.validator) + for _, ext_path in app_artifacts.dist_entries: + deploy_sources.append(app_artifacts.compact) + flipp_dist_paths.append(f"/ext/{ext_path}") + return app_artifacts + + def _add_host_app_to_targets(host_app): + artifacts_app_to_run = env["EXT_APPS"].get(host_app.appid, None) + _add_dist_targets(artifacts_app_to_run) + for plugin in host_app._plugins: + _add_dist_targets(env["EXT_APPS"].get(plugin.appid, None)) + + artifacts_app_to_run = env.GetExtAppByIdOrPath(appname) + if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN: + # We deploy host app instead + host_app = env["APPMGR"].get(artifacts_app_to_run.app.requires[0]) + + if host_app: + if host_app.apptype == FlipperAppType.EXTERNAL: + _add_host_app_to_targets(host_app) + else: + # host app is a built-in app + run_script_extra_ars = f"-a {host_app.name}" + _add_dist_targets(artifacts_app_to_run) + else: + raise UserError("Host app is unknown") + else: + _add_host_app_to_targets(artifacts_app_to_run.app) + + # print(deploy_sources, flipp_dist_paths) + env.PhonyTarget( + launch_target_name, + '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', + source=deploy_sources, + FLIPPER_FILE_TARGETS=flipp_dist_paths, + EXTRA_ARGS=run_script_extra_ars, + ) + env.Alias(launch_target_name, validators) + + def generate(env, **kw): env.SetDefault( EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}", @@ -410,10 +462,14 @@ def generate(env, **kw): EXT_APPS={}, # appid -> FlipperExternalAppInfo EXT_LIBS={}, _APP_ICONS=[], + _FAP_META_SECTION=_FAP_META_SECTION, + _FAP_FILEASSETS_SECTION=_FAP_FILEASSETS_SECTION, ) env.AddMethod(BuildAppElf) env.AddMethod(GetExtAppByIdOrPath) + env.AddMethod(AddAppLaunchTarget) + env.Append( BUILDERS={ "FapDist": Builder( diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 324819818c..90d0831eb4 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -38,13 +38,13 @@ def ProcessSdkDepends(env, filename): return depends -def prebuild_sdk_emitter(target, source, env): +def api_amalgam_emitter(target, source, env): target.append(env.ChangeFileExtension(target[0], ".d")) target.append(env.ChangeFileExtension(target[0], ".i.c")) return target, source -def prebuild_sdk_create_origin_file(target, source, env): +def api_amalgam_gen_origin_header(target, source, env): mega_file = env.subst("${TARGET}.c", target=target[0]) with open(mega_file, "wt") as sdk_c: sdk_c.write( @@ -87,6 +87,7 @@ def _wrap_scons_vars(self, vars: str): class SdkTreeBuilder: SDK_DIR_SUBST = "SDK_ROOT_DIR" SDK_APP_EP_SUBST = "SDK_APP_EP_SUBST" + HEADER_EXTENSIONS = [".h", ".hpp"] def __init__(self, env, target, source) -> None: self.env = env @@ -111,7 +112,10 @@ def _parse_sdk_depends(self): lines = LogicalLines(deps_f).readlines() _, depends = lines[0].split(":", 1) self.header_depends = list( - filter(lambda fname: fname.endswith(".h"), depends.split()), + filter( + lambda fname: any(map(fname.endswith, self.HEADER_EXTENSIONS)), + depends.split(), + ), ) self.header_depends.append(self.sdk_env.subst("${LINKER_SCRIPT_PATH}")) self.header_depends.append(self.sdk_env.subst("${SDK_DEFINITION}")) @@ -180,12 +184,12 @@ def deploy_action(self): self._generate_sdk_meta() -def deploy_sdk_tree_action(target, source, env): +def deploy_sdk_header_tree_action(target, source, env): sdk_tree = SdkTreeBuilder(env, target, source) return sdk_tree.deploy_action() -def deploy_sdk_tree_emitter(target, source, env): +def deploy_sdk_header_tree_emitter(target, source, env): sdk_tree = SdkTreeBuilder(env, target, source) return sdk_tree.emitter(target, source, env) @@ -224,7 +228,7 @@ def _check_sdk_is_up2date(sdk_cache: SdkCache): ) -def validate_sdk_cache(source, target, env): +def validate_api_cache(source, target, env): # print(f"Generating SDK for {source[0]} to {target[0]}") current_sdk = SdkCollector() current_sdk.process_source_file_for_sdk(source[0].path) @@ -237,7 +241,7 @@ def validate_sdk_cache(source, target, env): _check_sdk_is_up2date(sdk_cache) -def generate_sdk_symbols(source, target, env): +def generate_api_table(source, target, env): sdk_cache = SdkCache(source[0].path) _check_sdk_is_up2date(sdk_cache) @@ -249,11 +253,11 @@ def generate_sdk_symbols(source, target, env): def generate(env, **kw): if not env["VERBOSE"]: env.SetDefault( - SDK_PREGEN_COMSTR="\tPREGEN\t${TARGET}", - SDK_COMSTR="\tSDKSRC\t${TARGET}", + SDK_AMALGAMATE_HEADER_COMSTR="\tAPIPREP\t${TARGET}", + SDK_AMALGAMATE_PP_COMSTR="\tAPIPP\t${TARGET}", SDKSYM_UPDATER_COMSTR="\tSDKCHK\t${TARGET}", - SDKSYM_GENERATOR_COMSTR="\tSDKSYM\t${TARGET}", - SDKDEPLOY_COMSTR="\tSDKTREE\t${TARGET}", + APITABLE_GENERATOR_COMSTR="\tAPITBL\t${TARGET}", + SDKTREE_COMSTR="\tSDKTREE\t${TARGET}", ) # Filtering out things cxxheaderparser cannot handle @@ -274,40 +278,40 @@ def generate(env, **kw): env.AddMethod(ProcessSdkDepends) env.Append( BUILDERS={ - "SDKPrebuilder": Builder( - emitter=prebuild_sdk_emitter, + "ApiAmalgamator": Builder( + emitter=api_amalgam_emitter, action=[ Action( - prebuild_sdk_create_origin_file, - "$SDK_PREGEN_COMSTR", + api_amalgam_gen_origin_header, + "$SDK_AMALGAMATE_HEADER_COMSTR", ), Action( "$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c", - "$SDK_COMSTR", + "$SDK_AMALGAMATE_PP_COMSTR", ), ], suffix=".i", ), - "SDKTree": Builder( + "SDKHeaderTreeExtractor": Builder( action=Action( - deploy_sdk_tree_action, - "$SDKDEPLOY_COMSTR", + deploy_sdk_header_tree_action, + "$SDKTREE_COMSTR", ), - emitter=deploy_sdk_tree_emitter, + emitter=deploy_sdk_header_tree_emitter, src_suffix=".d", ), - "SDKSymUpdater": Builder( + "ApiTableValidator": Builder( action=Action( - validate_sdk_cache, + validate_api_cache, "$SDKSYM_UPDATER_COMSTR", ), suffix=".csv", src_suffix=".i", ), - "SDKSymGenerator": Builder( + "ApiSymbolTable": Builder( action=Action( - generate_sdk_symbols, - "$SDKSYM_GENERATOR_COMSTR", + generate_api_table, + "$APITABLE_GENERATOR_COMSTR", ), suffix=".h", src_suffix=".csv", diff --git a/scripts/fbt_tools/fbt_tweaks.py b/scripts/fbt_tools/fbt_tweaks.py index a903d4033b..700f66d238 100644 --- a/scripts/fbt_tools/fbt_tweaks.py +++ b/scripts/fbt_tools/fbt_tweaks.py @@ -1,4 +1,6 @@ import SCons.Warnings as Warnings +from SCons.Errors import UserError + # from SCons.Script.Main import find_deepest_user_frame @@ -36,6 +38,11 @@ def fbt_warning(e): def generate(env): + if env.get("UFBT_WORK_DIR"): + raise UserError( + "You're trying to use a new format SDK on a legacy ufbt version. " + "Please update ufbt to a version from PyPI: https://pypi.org/project/ufbt/" + ) Warnings._warningOut = fbt_warning diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 47e11236d1..7b56ee0d0e 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -56,11 +56,11 @@ def from_value(cls, s: str | bytes): class FlipperStorageException(Exception): - def __init__(self, message): - super().__init__(f"Storage error: {message}") - - def __init__(self, path: str, error_code: StorageErrorCode): - super().__init__(f"Storage error: path '{path}': {error_code.value}") + @staticmethod + def from_error_code(path: str, error_code: StorageErrorCode): + return FlipperStorageException( + f"Storage error: path '{path}': {error_code.value}" + ) class BufferedRead: @@ -247,7 +247,9 @@ def send_file(self, filename_from: str, filename_to: str): if self.has_error(answer): last_error = self.get_error(answer) self.read.until(self.CLI_PROMPT) - raise FlipperStorageException(filename_to, last_error) + raise FlipperStorageException.from_error_code( + filename_to, last_error + ) self.port.write(filedata) self.read.until(self.CLI_PROMPT) @@ -319,7 +321,7 @@ def exist_dir(self, path: str): StorageErrorCode.INVALID_NAME, ): return False - raise FlipperStorageException(path, error_code) + raise FlipperStorageException.from_error_code(path, error_code) return True @@ -333,7 +335,7 @@ def exist_file(self, path: str): def _check_no_error(self, response, path=None): if self.has_error(response): - raise FlipperStorageException(self.get_error(response)) + raise FlipperStorageException.from_error_code(self.get_error(response)) def size(self, path: str): """file size on Flipper""" diff --git a/scripts/get_env.py b/scripts/get_env.py index e2da6eda5c..f661f38d62 100644 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -31,9 +31,10 @@ def parse_args(): def get_commit_json(event): context = ssl._create_unverified_context() - with urllib.request.urlopen( - event["pull_request"]["_links"]["commits"]["href"], context=context - ) as commit_file: + commit_url = event["pull_request"]["base"]["repo"]["commits_url"].replace( + "{/sha}", f"/{event['after']}" + ) + with urllib.request.urlopen(commit_url, context=context) as commit_file: commit_json = json.loads(commit_file.read().decode("utf-8")) return commit_json @@ -43,8 +44,8 @@ def get_details(event, args): current_time = datetime.datetime.utcnow().date() if args.type == "pull": commit_json = get_commit_json(event) - data["commit_comment"] = shlex.quote(commit_json[-1]["commit"]["message"]) - data["commit_hash"] = commit_json[-1]["sha"] + data["commit_comment"] = shlex.quote(commit_json["commit"]["message"]) + data["commit_hash"] = commit_json["sha"] ref = event["pull_request"]["head"]["ref"] data["pull_id"] = event["pull_request"]["number"] data["pull_name"] = shlex.quote(event["pull_request"]["title"]) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index b8f1d72b26..c2cfecd4e5 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 -from flipper.app import App -from os.path import join, exists, relpath -from os import makedirs, walk -from update import Main as UpdateMain +import json import shutil -import zipfile import tarfile +import zipfile +from os import makedirs, walk +from os.path import exists, join, relpath, basename, split + from ansi.color import fg +from flipper.app import App +from update import Main as UpdateMain class ProjectDir: @@ -54,12 +56,19 @@ def get_project_file_name(self, project: ProjectDir, filetype: str) -> str: if project_name == "firmware" and filetype != "elf": project_name = "full" - return self.get_dist_file_name(project_name, filetype) + dist_target_path = self.get_dist_file_name(project_name, filetype) + self.note_dist_component( + project_name, filetype, self.get_dist_path(dist_target_path) + ) + return dist_target_path + + def note_dist_component(self, component: str, extension: str, srcpath: str) -> None: + self._dist_components[f"{component}.{extension}"] = srcpath def get_dist_file_name(self, dist_artifact_type: str, filetype: str) -> str: return f"{self.DIST_FILE_PREFIX}{self.target}-{dist_artifact_type}-{self.args.suffix}.{filetype}" - def get_dist_file_path(self, filename: str) -> str: + def get_dist_path(self, filename: str) -> str: return join(self.output_dir_path, filename) def copy_single_project(self, project: ProjectDir) -> None: @@ -69,17 +78,15 @@ def copy_single_project(self, project: ProjectDir) -> None: if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")): shutil.copyfile( src_file, - self.get_dist_file_path( - self.get_project_file_name(project, filetype) - ), + self.get_dist_path(self.get_project_file_name(project, filetype)), ) - for foldertype in ("sdk", "lib"): + for foldertype in ("sdk_headers", "lib"): if exists(sdk_folder := join(obj_directory, foldertype)): - self.package_zip(foldertype, sdk_folder) + self.note_dist_component(foldertype, "dir", sdk_folder) def package_zip(self, foldertype, sdk_folder): with zipfile.ZipFile( - self.get_dist_file_path(self.get_dist_file_name(foldertype, "zip")), + self.get_dist_path(self.get_dist_file_name(foldertype, "zip")), "w", zipfile.ZIP_DEFLATED, ) as zf: @@ -94,7 +101,8 @@ def package_zip(self, foldertype, sdk_folder): ) def copy(self) -> int: - self.projects = dict( + self._dist_components: dict[str, str] = dict() + self.projects: dict[str, ProjectDir] = dict( map( lambda pd: (pd.project, pd), map(ProjectDir, self.args.project), @@ -122,12 +130,18 @@ def copy(self) -> int: try: shutil.rmtree(self.output_dir_path) except Exception as ex: - pass + self.logger.warn(f"Failed to clean output directory: {ex}") if not exists(self.output_dir_path): + self.logger.debug(f"Creating output directory {self.output_dir_path}") makedirs(self.output_dir_path) + for folder in ("debug", "scripts"): + if exists(folder): + self.note_dist_component(folder, "dir", folder) + for project in self.projects.values(): + self.logger.debug(f"Copying {project.project} for {project.target}") self.copy_single_project(project) self.logger.info( @@ -137,58 +151,132 @@ def copy(self) -> int: ) if self.args.version: - bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[ - : self.DIST_FOLDER_MAX_NAME_LENGTH - ] - bundle_dir = join(self.output_dir_path, bundle_dir_name) - bundle_args = [ - "generate", - "-d", - bundle_dir, - "-v", - self.args.version, - "-t", - self.target, - "--dfu", - self.get_dist_file_path( - self.get_project_file_name(self.projects["firmware"], "dfu") - ), - "--stage", - self.get_dist_file_path( - self.get_project_file_name(self.projects["updater"], "bin") + if bundle_result := self.bundle_update_package(): + return bundle_result + + required_components = ("firmware.elf", "full.bin", "update.dir") + if all( + map( + lambda c: c in self._dist_components, + required_components, + ) + ): + self.bundle_sdk() + + return 0 + + def bundle_sdk(self): + self.logger.info("Bundling SDK") + components_paths = dict() + + sdk_components_keys = ( + "full.bin", + "firmware.elf", + "update.dir", + "sdk_headers.dir", + "lib.dir", + "debug.dir", + "scripts.dir", + ) + + with zipfile.ZipFile( + self.get_dist_path(self.get_dist_file_name("sdk", "zip")), + "w", + zipfile.ZIP_DEFLATED, + ) as zf: + for component_key in sdk_components_keys: + component_path = self._dist_components.get(component_key) + components_paths[component_key] = basename(component_path) + + if component_key.endswith(".dir"): + for root, dirnames, files in walk(component_path): + if "__pycache__" in dirnames: + dirnames.remove("__pycache__") + for file in files: + zf.write( + join(root, file), + join( + components_paths[component_key], + relpath( + join(root, file), + component_path, + ), + ), + ) + else: + zf.write(component_path, basename(component_path)) + + zf.writestr( + "components.json", + json.dumps( + { + "meta": { + "hw_target": self.target, + "flavor": self.flavor, + "version": self.args.version, + }, + "components": components_paths, + } ), - ] - if self.args.resources: - bundle_args.extend( - ( - "-r", - self.args.resources, - ) - ) - bundle_args.extend(self.other_args) + ) - if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: - self.logger.info( - fg.boldgreen( - f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" - ) + def bundle_update_package(self): + self.logger.debug( + f"Generating update bundle with version {self.args.version} for {self.target}" + ) + bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[ + : self.DIST_FOLDER_MAX_NAME_LENGTH + ] + bundle_dir = self.get_dist_path(bundle_dir_name) + bundle_args = [ + "generate", + "-d", + bundle_dir, + "-v", + self.args.version, + "-t", + self.target, + "--dfu", + self.get_dist_path( + self.get_project_file_name(self.projects["firmware"], "dfu") + ), + "--stage", + self.get_dist_path( + self.get_project_file_name(self.projects["updater"], "bin") + ), + ] + if self.args.resources: + bundle_args.extend( + ( + "-r", + self.args.resources, ) + ) + bundle_args.extend(self.other_args) - # Create tgz archive - with tarfile.open( - join( - self.output_dir_path, - f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz", - ), - "w:gz", - compresslevel=9, - format=tarfile.USTAR_FORMAT, - ) as tar: - tar.add(bundle_dir, arcname=bundle_dir_name) - - return bundle_result + if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: + self.note_dist_component("update", "dir", bundle_dir) + self.logger.info( + fg.boldgreen( + f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" + ) + ) - return 0 + # Create tgz archive + with tarfile.open( + join( + self.output_dir_path, + bundle_tgz := f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz", + ), + "w:gz", + compresslevel=9, + format=tarfile.USTAR_FORMAT, + ) as tar: + self.note_dist_component( + "update", "tgz", self.get_dist_path(bundle_tgz) + ) + tar.add(bundle_dir, arcname=bundle_dir_name) + return bundle_result if __name__ == "__main__": diff --git a/scripts/serial_cli.py b/scripts/serial_cli.py index 441bc7cc8e..390b1f2638 100644 --- a/scripts/serial_cli.py +++ b/scripts/serial_cli.py @@ -8,7 +8,7 @@ def main(): logger = logging.getLogger() if not (port := resolve_port(logger, "auto")): - logger.error("Is Flipper connected over USB and isn't in DFU mode?") + logger.error("Is Flipper connected over USB and is it not in DFU mode?") return 1 subprocess.call( [ diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 8587f6d0e0..9d45b7e9d7 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -15,10 +15,12 @@ if not ["%FBT_NOENV%"] == [""] ( set "FLIPPER_TOOLCHAIN_VERSION=21" -if ["%FBT_TOOLCHAIN_ROOT%"] == [""] ( - set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" +if ["%FBT_TOOLCHAIN_PATH%"] == [""] ( + set "FBT_TOOLCHAIN_PATH=%FBT_ROOT%" ) +set "FBT_TOOLCHAIN_ROOT=%FBT_TOOLCHAIN_PATH%\toolchain\x86_64-windows" + set "FBT_TOOLCHAIN_VERSION_FILE=%FBT_TOOLCHAIN_ROOT%\VERSION" if not exist "%FBT_TOOLCHAIN_ROOT%" ( diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 8f05c23ca8..57a50281ed 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -4,9 +4,15 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; -SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"21"}"; -FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; + +if [ -z ${FBT_TOOLCHAIN_PATH+x} ] ; then + FBT_TOOLCHAIN_PATH_WAS_SET=0; +else + FBT_TOOLCHAIN_PATH_WAS_SET=1; +fi + +FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$DEFAULT_SCRIPT_PATH}"; FBT_VERBOSE="${FBT_VERBOSE:-""}"; fbtenv_show_usage() @@ -60,7 +66,6 @@ fbtenv_restore_env() unset SAVED_PYTHONPATH; unset SAVED_PYTHONHOME; - unset SCRIPT_PATH; unset FBT_TOOLCHAIN_VERSION; unset FBT_TOOLCHAIN_PATH; } @@ -104,13 +109,14 @@ fbtenv_set_shell_prompt() return 0; # all other shells } -fbtenv_check_script_path() +fbtenv_check_env_vars() { - if [ ! -x "$SCRIPT_PATH/fbt" ] && [ ! -x "$SCRIPT_PATH/ufbt" ] ; then - echo "Please source this script from [u]fbt root directory, or specify 'SCRIPT_PATH' variable manually"; + # Return error if FBT_TOOLCHAIN_PATH is not set before script is sourced or if fbt executable is not in DEFAULT_SCRIPT_PATH + if [ "$FBT_TOOLCHAIN_PATH_WAS_SET" -eq 0 ] && [ ! -x "$DEFAULT_SCRIPT_PATH/fbt" ] && [ ! -x "$DEFAULT_SCRIPT_PATH/ufbt" ] ; then + echo "Please source this script from [u]fbt root directory, or specify 'FBT_TOOLCHAIN_PATH' variable manually"; echo "Example:"; - printf "\tSCRIPT_PATH=lang/c/flipperzero-firmware source lang/c/flipperzero-firmware/scripts/fbtenv.sh\n"; - echo "If current directory is right, type 'unset SCRIPT_PATH' and try again" + printf "\tFBT_TOOLCHAIN_PATH=lang/c/flipperzero-firmware source lang/c/flipperzero-firmware/scripts/fbtenv.sh\n"; + echo "If current directory is right, type 'unset FBT_TOOLCHAIN_PATH' and try again" return 1; fi return 0; @@ -207,7 +213,7 @@ fbtenv_show_unpack_percentage() fbtenv_unpack_toolchain() { - echo "Unpacking toolchain:"; + echo "Unpacking toolchain to '$FBT_TOOLCHAIN_PATH/toolchain':"; tar -xvf "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" -C "$FBT_TOOLCHAIN_PATH/toolchain" 2>&1 | fbtenv_show_unpack_percentage; mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_DIR" "$TOOLCHAIN_ARCH_DIR" || return 1; @@ -215,7 +221,7 @@ fbtenv_unpack_toolchain() return 0; } -fbtenv_clearing() +fbtenv_cleanup() { printf "Cleaning up.."; if [ -n "${FBT_TOOLCHAIN_PATH:-""}" ]; then @@ -270,14 +276,14 @@ fbtenv_download_toolchain() fbtenv_check_tar || return 1; TOOLCHAIN_TAR="$(basename "$TOOLCHAIN_URL")"; TOOLCHAIN_DIR="$(echo "$TOOLCHAIN_TAR" | sed "s/-$FBT_TOOLCHAIN_VERSION.tar.gz//g")"; - trap fbtenv_clearing 2; # trap will be restored in fbtenv_clearing + trap fbtenv_cleanup 2; # trap will be restored in fbtenv_cleanup if ! fbtenv_check_downloaded_toolchain; then fbtenv_curl_wget_check || return 1; fbtenv_download_toolchain_tar || return 1; fi fbtenv_remove_old_tooclhain; fbtenv_unpack_toolchain || return 1; - fbtenv_clearing; + fbtenv_cleanup; return 0; } @@ -296,8 +302,8 @@ fbtenv_main() fbtenv_restore_env; return 0; fi - fbtenv_check_if_sourced_multiple_times; # many source it's just a warning - fbtenv_check_script_path || return 1; + fbtenv_check_if_sourced_multiple_times; + fbtenv_check_env_vars || return 1; fbtenv_check_download_toolchain || return 1; fbtenv_set_shell_prompt; fbtenv_print_version; diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct new file mode 100644 index 0000000000..a82189c147 --- /dev/null +++ b/scripts/ufbt/SConstruct @@ -0,0 +1,393 @@ +from SCons.Platform import TempFileMunge +from SCons.Node import FS +from SCons.Errors import UserError + +import os +import multiprocessing +import pathlib + +SetOption("num_jobs", multiprocessing.cpu_count()) +SetOption("max_drift", 1) +# SetOption("silent", False) + +ufbt_state_dir = Dir(os.environ.get("UFBT_STATE_DIR", "#.ufbt")) +ufbt_script_dir = Dir(os.environ.get("UFBT_SCRIPT_DIR")) + +ufbt_current_sdk_dir = ufbt_state_dir.Dir("current") + +SConsignFile(ufbt_state_dir.File(".sconsign.dblite").abspath) + +ufbt_variables = SConscript("commandline.scons") + +forward_os_env = { + # Import PATH from OS env - scons doesn't do that by default + "PATH": os.environ["PATH"], +} + +# Proxying environment to child processes & scripts +variables_to_forward = [ + # CI/CD variables + "WORKFLOW_BRANCH_OR_TAG", + "DIST_SUFFIX", + # Python & other tools + "HOME", + "APPDATA", + "PYTHONHOME", + "PYTHONNOUSERSITE", + "TMP", + "TEMP", + # Colors for tools + "TERM", +] + +if proxy_env := GetOption("proxy_env"): + variables_to_forward.extend(proxy_env.split(",")) + +for env_value_name in variables_to_forward: + if environ_value := os.environ.get(env_value_name, None): + forward_os_env[env_value_name] = environ_value + +# Core environment init - loads SDK state, sets up paths, etc. +core_env = Environment( + variables=ufbt_variables, + ENV=forward_os_env, + UFBT_STATE_DIR=ufbt_state_dir, + UFBT_CURRENT_SDK_DIR=ufbt_current_sdk_dir, + UFBT_SCRIPT_DIR=ufbt_script_dir, + toolpath=[ufbt_current_sdk_dir.Dir("scripts/ufbt/site_tools")], + tools=[ + "ufbt_state", + ("ufbt_help", {"vars": ufbt_variables}), + ], +) + +if "update" in BUILD_TARGETS: + SConscript( + "update.scons", + exports={"core_env": core_env}, + ) + +if "purge" in BUILD_TARGETS: + core_env.Execute(Delete(ufbt_state_dir)) + print("uFBT state purged") + Exit(0) + +# Now we can import stuff bundled with SDK - it was added to sys.path by ufbt_state + +from fbt.util import ( + tempfile_arg_esc_func, + single_quote, + extract_abs_dir, + extract_abs_dir_path, + wrap_tempfile, + path_as_posix, +) +from fbt.appmanifest import FlipperAppType +from fbt.sdk.cache import SdkCache + +# Base environment with all tools loaded from SDK +env = core_env.Clone( + toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")], + tools=[ + "fbt_tweaks", + ( + "crosscc", + { + "toolchain_prefix": "arm-none-eabi-", + "versions": (" 10.3",), + }, + ), + "fwbin", + "python3", + "sconsrecursiveglob", + "sconsmodular", + "ccache", + "fbt_apps", + "fbt_extapps", + "fbt_assets", + ("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}), + ], + FBT_FAP_DEBUG_ELF_ROOT=ufbt_state_dir.Dir("build"), + TEMPFILE=TempFileMunge, + MAXLINELENGTH=2048, + PROGSUFFIX=".elf", + TEMPFILEARGESCFUNC=tempfile_arg_esc_func, + SINGLEQUOTEFUNC=single_quote, + ABSPATHGETTERFUNC=extract_abs_dir_path, + APPS=[], + UFBT_API_VERSION=SdkCache( + core_env.subst("$SDK_DEFINITION"), load_version_only=True + ).version, + APPCHECK_COMSTR="\tAPPCHK\t${SOURCE}\n\t\tTarget: ${TARGET_HW}, API: ${UFBT_API_VERSION}", +) + +wrap_tempfile(env, "LINKCOM") +wrap_tempfile(env, "ARCOM") + +# print(env.Dump()) + +# Dist env + +dist_env = env.Clone( + tools=[ + "fbt_dist", + "fbt_debugopts", + "openocd", + "blackmagic", + "jflash", + "textfile", + ], + ENV=os.environ, + OPENOCD_OPTS=[ + "-f", + "interface/stlink.cfg", + "-c", + "transport select hla_swd", + "-f", + "${FBT_DEBUG_DIR}/stm32wbx.cfg", + "-c", + "stm32wbx.cpu configure -rtos auto", + ], +) + +openocd_target = dist_env.OpenOCDFlash( + dist_env["UFBT_STATE_DIR"].File("flash"), + dist_env["FW_BIN"], + OPENOCD_COMMAND=[ + "-c", + "program ${SOURCE.posix} reset exit 0x08000000", + ], +) +dist_env.Alias("firmware_flash", openocd_target) +dist_env.Alias("flash", openocd_target) +if env["FORCE"]: + env.AlwaysBuild(openocd_target) + +firmware_debug = dist_env.PhonyTarget( + "debug", + "${GDBPYCOM}", + source=dist_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE}", + GDBREMOTE="${OPENOCD_GDB_PIPE}", + FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), +) + +dist_env.PhonyTarget( + "blackmagic", + "${GDBPYCOM}", + source=dist_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", + GDBREMOTE="${BLACKMAGIC_ADDR}", + FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), +) + +dist_env.PhonyTarget( + "flash_blackmagic", + "$GDB $GDBOPTS $SOURCES $GDBFLASH", + source=dist_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", + GDBREMOTE="${BLACKMAGIC_ADDR}", + GDBFLASH=[ + "-ex", + "load", + "-ex", + "quit", + ], +) + +flash_usb_full = dist_env.UsbInstall( + dist_env["UFBT_STATE_DIR"].File("usbinstall"), + [], +) +dist_env.AlwaysBuild(flash_usb_full) +dist_env.Alias("flash_usb", flash_usb_full) +dist_env.Alias("flash_usb_full", flash_usb_full) + +# App build environment + +appenv = env.Clone( + CCCOM=env["CCCOM"].replace("$CFLAGS", "$CFLAGS_APP $CFLAGS"), + CXXCOM=env["CXXCOM"].replace("$CXXFLAGS", "$CXXFLAGS_APP $CXXFLAGS"), + LINKCOM=env["LINKCOM"].replace("$LINKFLAGS", "$LINKFLAGS_APP $LINKFLAGS"), + COMPILATIONDB_USE_ABSPATH=True, +) + + +original_app_dir = Dir(appenv.subst("$UFBT_APP_DIR")) +app_mount_point = Dir("#/app/") +app_mount_point.addRepository(original_app_dir) + +appenv.LoadAppManifest(app_mount_point) +appenv.PrepareApplicationsBuild() + +####################### + +apps_artifacts = appenv["EXT_APPS"] + +apps_to_build_as_faps = [ + FlipperAppType.PLUGIN, + FlipperAppType.EXTERNAL, +] + +known_extapps = [ + app + for apptype in apps_to_build_as_faps + for app in appenv["APPBUILD"].get_apps_of_type(apptype, True) +] +for app in known_extapps: + app_artifacts = appenv.BuildAppElf(app) + app_src_dir = extract_abs_dir(app_artifacts.app._appdir) + app_artifacts.installer = [ + appenv.Install(app_src_dir.Dir("dist"), app_artifacts.compact), + appenv.Install(app_src_dir.Dir("dist").Dir("debug"), app_artifacts.debug), + ] + +if appenv["FORCE"]: + appenv.AlwaysBuild([extapp.compact for extapp in apps_artifacts.values()]) + +# Final steps - target aliases + +install_and_check = [ + (extapp.installer, extapp.validator) for extapp in apps_artifacts.values() +] +Alias( + "faps", + install_and_check, +) +Default(install_and_check) + +# Compilation database + +fwcdb = appenv.CompilationDatabase( + original_app_dir.Dir(".vscode").File("compile_commands.json") +) + +AlwaysBuild(fwcdb) +Precious(fwcdb) +NoClean(fwcdb) +if len(apps_artifacts): + Default(fwcdb) + + +# launch handler +runnable_apps = appenv["APPBUILD"].get_apps_of_type(FlipperAppType.EXTERNAL, True) + +app_to_launch = None +if len(runnable_apps) == 1: + app_to_launch = runnable_apps[0].appid +elif len(runnable_apps) > 1: + # more than 1 app - try to find one with matching id + app_to_launch = appenv.subst("$APPID") + + +def ambiguous_app_call(**kw): + raise UserError( + f"More than one app is runnable: {', '.join(app.appid for app in runnable_apps)}. Please specify an app with APPID=..." + ) + + +if app_to_launch: + appenv.AddAppLaunchTarget(app_to_launch, "launch") +else: + dist_env.PhonyTarget("launch", Action(ambiguous_app_call, None)) + +# cli handler + +appenv.PhonyTarget( + "cli", + '${PYTHON3} "${FBT_SCRIPT_DIR}/serial_cli.py"', +) + +# Linter + +dist_env.PhonyTarget( + "lint", + "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}", + source=original_app_dir.File(".clang-format"), + LINT_SOURCES=[original_app_dir], +) + +dist_env.PhonyTarget( + "format", + "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py format ${LINT_SOURCES}", + source=original_app_dir.File(".clang-format"), + LINT_SOURCES=[original_app_dir], +) + + +# Prepare vscode environment +def _path_as_posix(path): + return pathlib.Path(path).as_posix() + + +vscode_dist = [] +project_template_dir = dist_env["UFBT_SCRIPT_ROOT"].Dir("project_template") +for template_file in project_template_dir.Dir(".vscode").glob("*"): + vscode_dist.append( + dist_env.Substfile( + original_app_dir.Dir(".vscode").File(template_file.name), + template_file, + SUBST_DICT={ + "@UFBT_VSCODE_PATH_SEP@": os.path.pathsep, + "@UFBT_TOOLCHAIN_ARM_TOOLCHAIN_DIR@": pathlib.Path( + dist_env.WhereIs("arm-none-eabi-gcc") + ).parent.as_posix(), + "@UFBT_TOOLCHAIN_GCC@": _path_as_posix( + dist_env.WhereIs("arm-none-eabi-gcc") + ), + "@UFBT_TOOLCHAIN_GDB_PY@": _path_as_posix( + dist_env.WhereIs("arm-none-eabi-gdb-py") + ), + "@UFBT_TOOLCHAIN_OPENOCD@": _path_as_posix(dist_env.WhereIs("openocd")), + "@UFBT_APP_DIR@": _path_as_posix(original_app_dir.abspath), + "@UFBT_ROOT_DIR@": _path_as_posix(Dir("#").abspath), + "@UFBT_DEBUG_DIR@": dist_env["FBT_DEBUG_DIR"], + "@UFBT_DEBUG_ELF_DIR@": _path_as_posix( + dist_env["FBT_FAP_DEBUG_ELF_ROOT"].abspath + ), + "@UFBT_FIRMWARE_ELF@": _path_as_posix(dist_env["FW_ELF"].abspath), + }, + ) + ) + +for config_file in project_template_dir.glob(".*"): + if isinstance(config_file, FS.Dir): + continue + vscode_dist.append(dist_env.Install(original_app_dir, config_file)) + +dist_env.Precious(vscode_dist) +dist_env.NoClean(vscode_dist) +dist_env.Alias("vscode_dist", vscode_dist) + + +# Creating app from base template + +dist_env.SetDefault(FBT_APPID=appenv.subst("$APPID") or "template") +app_template_dist = [] +for template_file in project_template_dir.Dir("app_template").glob("*"): + dist_file_name = dist_env.subst(template_file.name) + if template_file.name.endswith(".png"): + app_template_dist.append( + dist_env.InstallAs(original_app_dir.File(dist_file_name), template_file) + ) + else: + app_template_dist.append( + dist_env.Substfile( + original_app_dir.File(dist_file_name), + template_file, + SUBST_DICT={ + "@FBT_APPID@": dist_env.subst("$FBT_APPID"), + }, + ) + ) + +AddPostAction( + app_template_dist[-1], + [ + Mkdir(original_app_dir.Dir("images")), + Touch(original_app_dir.Dir("images").File(".gitkeep")), + ], +) +dist_env.Precious(app_template_dist) +dist_env.NoClean(app_template_dist) +dist_env.Alias("create", app_template_dist) diff --git a/scripts/ufbt/commandline.scons b/scripts/ufbt/commandline.scons new file mode 100644 index 0000000000..9af5e9bce9 --- /dev/null +++ b/scripts/ufbt/commandline.scons @@ -0,0 +1,90 @@ +AddOption( + "--proxy-env", + action="store", + dest="proxy_env", + default="", + help="Comma-separated list of additional environment variables to pass to child SCons processes", +) + +AddOption( + "--channel", + action="store", + dest="sdk_channel", + choices=["dev", "rc", "release"], + default="", + help="Release channel to use for SDK", +) + +AddOption( + "--branch", + action="store", + dest="sdk_branch", + help="Custom main repo branch to use for SDK", +) + +AddOption( + "--hw-target", + action="store", + dest="sdk_target", + help="SDK Hardware target", +) + +vars = Variables("ufbt_options.py", ARGUMENTS) + +vars.AddVariables( + BoolVariable( + "VERBOSE", + help="Print full commands", + default=False, + ), + BoolVariable( + "FORCE", + help="Force target action (for supported targets)", + default=False, + ), + # These 2 are inherited from SDK + # BoolVariable( + # "DEBUG", + # help="Enable debug build", + # default=True, + # ), + # BoolVariable( + # "COMPACT", + # help="Optimize for size", + # default=False, + # ), + PathVariable( + "OTHER_ELF", + help="Path to prebuilt ELF file to debug", + validator=PathVariable.PathAccept, + default="", + ), + ( + "OPENOCD_OPTS", + "Options to pass to OpenOCD", + "", + ), + ( + "BLACKMAGIC", + "Blackmagic probe location", + "auto", + ), + ( + "OPENOCD_ADAPTER_SERIAL", + "OpenOCD adapter serial number", + "auto", + ), + ( + "APPID", + "Application id", + "", + ), + PathVariable( + "UFBT_APP_DIR", + help="Application dir to work with", + validator=PathVariable.PathIsDir, + default="", + ), +) + +Return("vars") diff --git a/scripts/ufbt/project_template/.clang-format b/scripts/ufbt/project_template/.clang-format new file mode 100644 index 0000000000..4b76f7fa43 --- /dev/null +++ b/scripts/ufbt/project_template/.clang-format @@ -0,0 +1,191 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 99 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: BinPack +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: true +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 10 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: false +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortJavaStaticImport: Before +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: Never +SpaceBeforeParensOptions: + AfterControlStatements: false + AfterForeachMacros: false + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: false + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: c++03 +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/scripts/ufbt/project_template/.editorconfig b/scripts/ufbt/project_template/.editorconfig new file mode 100644 index 0000000000..a31ef8e753 --- /dev/null +++ b/scripts/ufbt/project_template/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.{cpp,h,c,py,sh}] +indent_style = space +indent_size = 4 + +[{Makefile,*.mk}] +indent_size = tab diff --git a/scripts/ufbt/project_template/.gitignore b/scripts/ufbt/project_template/.gitignore new file mode 100644 index 0000000000..e2a15a10a8 --- /dev/null +++ b/scripts/ufbt/project_template/.gitignore @@ -0,0 +1,4 @@ +dist/* +.vscode +.clang-format +.editorconfig \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/c_cpp_properties.json b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json new file mode 100644 index 0000000000..922a9091b6 --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json @@ -0,0 +1,14 @@ +{ + "configurations": [ + { + "name": "main", + "compilerPath": "@UFBT_TOOLCHAIN_GCC@", + "intelliSenseMode": "gcc-arm", + "compileCommands": "${workspaceFolder}/.vscode/compile_commands.json", + "configurationProvider": "ms-vscode.cpptools", + "cStandard": "gnu17", + "cppStandard": "c++17" + }, + ], + "version": 4 +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/extensions.json b/scripts/ufbt/project_template/.vscode/extensions.json new file mode 100644 index 0000000000..35f90700a3 --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/extensions.json @@ -0,0 +1,18 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "ms-python.black-formatter", + "ms-vscode.cpptools", + "amiralizadeh9480.cpp-helper", + "marus25.cortex-debug", + "zxh404.vscode-proto3", + "augustocdias.tasks-shell-input" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + "twxs.cmake", + "ms-vscode.cmake-tools" + ] +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/launch.json b/scripts/ufbt/project_template/.vscode/launch.json new file mode 100644 index 0000000000..d9c98dcc13 --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/launch.json @@ -0,0 +1,98 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "inputs": [ + // { + // "id": "BLACKMAGIC", + // "type": "command", + // "command": "shellCommand.execute", + // "args": { + // "useSingleResult": true, + // "env": { + // "PATH": "${workspaceFolder};${env:PATH}" + // }, + // "command": "./fbt get_blackmagic", + // "description": "Get Blackmagic device", + // } + // }, + ], + "configurations": [ + { + "name": "Attach FW (ST-Link)", + "cwd": "${workspaceFolder}", + "executable": "@UFBT_FIRMWARE_ELF@", + "request": "attach", + "type": "cortex-debug", + "servertype": "openocd", + "device": "stlink", + "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "configFiles": [ + "interface/stlink.cfg", + "@UFBT_DEBUG_DIR@/stm32wbx.cfg" + ], + "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperapps.py", + "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + ], + // "showDevDebugOutput": "raw", + }, + { + "name": "Attach FW (DAP)", + "cwd": "${workspaceFolder}", + "executable": "@UFBT_FIRMWARE_ELF@", + "request": "attach", + "type": "cortex-debug", + "servertype": "openocd", + "device": "cmsis-dap", + "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "configFiles": [ + "interface/cmsis-dap.cfg", + "@UFBT_DEBUG_DIR@/stm32wbx.cfg" + ], + "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperapps.py", + "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + ], + // "showDevDebugOutput": "raw", + }, + // { + // "name": "Attach FW (blackmagic)", + // "cwd": "${workspaceFolder}", + // "executable": "@UFBT_FIRMWARE_ELF@", + // "request": "attach", + // "type": "cortex-debug", + // "servertype": "external", + // "gdbTarget": "${input:BLACKMAGIC}", + // "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + // "rtos": "FreeRTOS", + // "postAttachCommands": [ + // "monitor swdp_scan", + // "attach 1", + // "set confirm off", + // "set mem inaccessible-by-default off", + // "source @UFBT_DEBUG_DIR@/flipperapps.py", + // "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + // ] + // // "showDevDebugOutput": "raw", + // }, + { + "name": "Attach FW (JLink)", + "cwd": "${workspaceFolder}", + "executable": "@UFBT_FIRMWARE_ELF@", + "request": "attach", + "type": "cortex-debug", + "servertype": "jlink", + "interface": "swd", + "device": "STM32WB55RG", + "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperapps.py", + "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + ] + // "showDevDebugOutput": "raw", + }, + ] +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/settings.json b/scripts/ufbt/project_template/.vscode/settings.json new file mode 100644 index 0000000000..33cd3f035e --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "cortex-debug.enableTelemetry": false, + "cortex-debug.variableUseNaturalFormat": false, + "cortex-debug.showRTOS": true, + "cortex-debug.armToolchainPath": "@UFBT_TOOLCHAIN_ARM_TOOLCHAIN_DIR@", + "cortex-debug.openocdPath": "@UFBT_TOOLCHAIN_OPENOCD@", + "cortex-debug.gdbPath": "@UFBT_TOOLCHAIN_GDB_PY@", + "editor.formatOnSave": true, + "files.associations": { + "*.scons": "python", + "SConscript": "python", + "SConstruct": "python", + "*.fam": "python" + }, + "cortex-debug.registerUseNaturalFormat": false, + "python.analysis.typeCheckingMode": "off", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/tasks.json b/scripts/ufbt/project_template/.vscode/tasks.json new file mode 100644 index 0000000000..6343bba7be --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/tasks.json @@ -0,0 +1,54 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "options": { + "env": { + "PATH": "${workspaceFolder}@UFBT_VSCODE_PATH_SEP@${env:PATH}@UFBT_VSCODE_PATH_SEP@@UFBT_ROOT_DIR@" + } + }, + "tasks": [ + { + "label": "Launch App on Flipper", + "group": "build", + "type": "shell", + "command": "ufbt launch" + }, + { + "label": "Build", + "group": "build", + "type": "shell", + "command": "ufbt" + }, + { + "label": "Flash FW (ST-Link)", + "group": "build", + "type": "shell", + "command": "ufbt FORCE=1 flash" + }, + // { + // "label": "[NOTIMPL] Flash FW (blackmagic)", + // "group": "build", + // "type": "shell", + // "command": "ufbt flash_blackmagic" + // }, + // { + // "label": "[NOTIMPL] Flash FW (JLink)", + // "group": "build", + // "type": "shell", + // "command": "ufbt FORCE=1 jflash" + // }, + { + "label": "Flash FW (USB, with resources)", + "group": "build", + "type": "shell", + "command": "ufbt FORCE=1 flash_usb" + }, + { + "label": "Update uFBT SDK", + "group": "build", + "type": "shell", + "command": "ufbt update" + } + ] +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/app_template/${FBT_APPID}.c b/scripts/ufbt/project_template/app_template/${FBT_APPID}.c new file mode 100644 index 0000000000..9b8113cb50 --- /dev/null +++ b/scripts/ufbt/project_template/app_template/${FBT_APPID}.c @@ -0,0 +1,12 @@ +#include + +/* generated by fbt from .png files in images folder */ +#include <@FBT_APPID@_icons.h> + +int32_t @FBT_APPID@_app(void* p) { + UNUSED(p); + FURI_LOG_I("TEST", "Hello world"); + FURI_LOG_I("TEST", "I'm @FBT_APPID@!"); + + return 0; +} diff --git a/scripts/ufbt/project_template/app_template/${FBT_APPID}.png b/scripts/ufbt/project_template/app_template/${FBT_APPID}.png new file mode 100644 index 0000000000000000000000000000000000000000..59e6c185f60aa7d0af126f96c9c9ff2ebf7d8ced GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih?TfNTy1#`a7G79fieh^2rSsBHlgT!d=@GlC70l-MxqJdpDDba4!kkSy&Ds2Gx}>HG7H0_ ovK6i!oilCimkTR6j_o_`>A%fhS~xer_cF)|Pgg&ebxsLQ00l-kGXMYp literal 0 HcmV?d00001 diff --git a/scripts/ufbt/project_template/app_template/application.fam b/scripts/ufbt/project_template/app_template/application.fam new file mode 100644 index 0000000000..31fadb207a --- /dev/null +++ b/scripts/ufbt/project_template/app_template/application.fam @@ -0,0 +1,17 @@ +# For details & more options, see documentation/AppManifests.md in firmware repo + +App( + appid="@FBT_APPID@", # Must be unique + name="App @FBT_APPID@", # Displayed in menus + apptype=FlipperAppType.EXTERNAL, + entry_point="@FBT_APPID@_app", + stack_size=2 * 1024, + fap_category="Misc", + # Optional values + # fap_version=(0, 1), # (major, minor) + fap_icon="@FBT_APPID@.png", # 10x10 1-bit PNG + # fap_description="A simple app", + # fap_author="J. Doe", + # fap_weburl="https://github.com/user/@FBT_APPID@", + fap_icon_assets="images", # Image assets to compile for this application +) diff --git a/scripts/ufbt/site_init.py b/scripts/ufbt/site_init.py new file mode 100644 index 0000000000..557085ede8 --- /dev/null +++ b/scripts/ufbt/site_init.py @@ -0,0 +1,36 @@ +from SCons.Script import GetBuildFailures +import SCons.Errors + +import atexit +from ansi.color import fg, fx + + +def bf_to_str(bf): + """Convert an element of GetBuildFailures() to a string + in a useful way.""" + + if bf is None: # unknown targets product None in list + return "(unknown tgt)" + elif isinstance(bf, SCons.Errors.StopError): + return fg.yellow(str(bf)) + elif bf.node: + return fg.yellow(str(bf.node)) + ": " + bf.errstr + elif bf.filename: + return fg.yellow(bf.filename) + ": " + bf.errstr + return fg.yellow("unknown failure: ") + bf.errstr + + +def display_build_status(): + """Display the build status. Called by atexit. + Here you could do all kinds of complicated things.""" + bf = GetBuildFailures() + if bf: + # bf is normally a list of build failures; if an element is None, + # it's because of a target that scons doesn't know anything about. + failures_message = "\n".join([bf_to_str(x) for x in bf if x is not None]) + print() + print(fg.brightred(fx.bold("*" * 10 + " FBT ERRORS " + "*" * 10))) + print(failures_message) + + +atexit.register(display_build_status) diff --git a/scripts/ufbt/site_tools/ufbt_help.py b/scripts/ufbt/site_tools/ufbt_help.py new file mode 100644 index 0000000000..da6ff6e51f --- /dev/null +++ b/scripts/ufbt/site_tools/ufbt_help.py @@ -0,0 +1,53 @@ +targets_help = """Configuration variables: +""" + +tail_help = """ + +TASKS: + (* - not supported yet) + + launch: + Upload and start application over USB + vscode_dist: + Configure application in current directory for development in VSCode. + create: + Copy application template to current directory. Set APPID=myapp to create an app with id 'myapp'. + +Building: + faps: + Build all FAP apps + fap_{APPID}, launch APPSRC={APPID}: + Build FAP app with appid={APPID}; upload & start it over USB + +Flashing & debugging: + flash, flash_blackmagic, *jflash: + Flash firmware to target using debug probe + flash_usb, flash_usb_full: + Install firmware using self-update package + debug, debug_other, blackmagic: + Start GDB + +Other: + cli: + Open a Flipper CLI session over USB + lint: + run linter for C code + format: + reformat C code + +How to create a new application: + 1. Create a new directory for your application and cd into it. + 2. Run `ufbt vscode_dist create APPID=myapp` + 3. In VSCode, open the folder and start editing. + 4. Run `ufbt launch` to build and upload your application. +""" + + +def generate(env, **kw): + vars = kw["vars"] + basic_help = vars.GenerateHelpText(env) + env.Help(targets_help + basic_help + tail_help) + + +def exists(env): + return True diff --git a/scripts/ufbt/site_tools/ufbt_state.py b/scripts/ufbt/site_tools/ufbt_state.py new file mode 100644 index 0000000000..6ba8c69628 --- /dev/null +++ b/scripts/ufbt/site_tools/ufbt_state.py @@ -0,0 +1,117 @@ +from SCons.Errors import StopError +from SCons.Warnings import warn, WarningOnByDefault + +import json +import os +import sys +import pathlib +from functools import reduce + + +def _load_sdk_data(sdk_root): + split_vars = { + "cc_args", + "cpp_args", + "linker_args", + "linker_libs", + } + subst_vars = split_vars | { + "sdk_symbols", + } + sdk_data = {} + with open(os.path.join(sdk_root, "sdk.opts")) as f: + sdk_json_data = json.load(f) + replacements = { + sdk_json_data["app_ep_subst"]: "${APP_ENTRY}", + sdk_json_data["sdk_path_subst"]: sdk_root.replace("\\", "/"), + sdk_json_data["map_file_subst"]: "${TARGET}", + } + + def do_value_substs(src_value): + if isinstance(src_value, str): + return reduce( + lambda acc, kv: acc.replace(*kv), replacements.items(), src_value + ) + elif isinstance(src_value, list): + return [do_value_substs(v) for v in src_value] + else: + return src_value + + for key, value in sdk_json_data.items(): + if key in split_vars: + value = value.split() + if key in subst_vars: + value = do_value_substs(value) + sdk_data[key] = value + + return sdk_data + + +def _load_state_file(state_dir_node, filename: str) -> dict: + state_path = os.path.join(state_dir_node.abspath, filename) + if not os.path.exists(state_path): + raise StopError(f"State file {state_path} not found") + + with open(state_path, "r") as f: + return json.load(f) + + +def generate(env, **kw): + sdk_current_sdk_dir_node = env["UFBT_CURRENT_SDK_DIR"] + + sdk_components_filename = kw.get("SDK_COMPONENTS", "components.json") + ufbt_state_filename = kw.get("UFBT_STATE", "ufbt_state.json") + + sdk_state = _load_state_file(sdk_current_sdk_dir_node, sdk_components_filename) + ufbt_state = _load_state_file(sdk_current_sdk_dir_node, ufbt_state_filename) + + if not (sdk_components := sdk_state.get("components", {})): + raise StopError("SDK state file doesn't contain components data") + + sdk_data = _load_sdk_data( + sdk_current_sdk_dir_node.Dir(sdk_components["sdk_headers.dir"]).abspath + ) + + if not sdk_state["meta"]["hw_target"].endswith(sdk_data["hardware"]): + raise StopError("SDK state file doesn't match hardware target") + + if sdk_state["meta"]["version"] != ufbt_state["version"]: + warn( + WarningOnByDefault, + f"Version mismatch: SDK state vs uFBT: {sdk_state['meta']['version']} vs {ufbt_state['version']}", + ) + + scripts_dir = sdk_current_sdk_dir_node.Dir(sdk_components["scripts.dir"]) + env.SetDefault( + # Paths + SDK_DEFINITION=env.File(sdk_data["sdk_symbols"]), + FBT_DEBUG_DIR=pathlib.Path( + sdk_current_sdk_dir_node.Dir(sdk_components["debug.dir"]).abspath + ).as_posix(), + FBT_SCRIPT_DIR=scripts_dir, + LIBPATH=sdk_current_sdk_dir_node.Dir(sdk_components["lib.dir"]), + FW_ELF=sdk_current_sdk_dir_node.File(sdk_components["firmware.elf"]), + FW_BIN=sdk_current_sdk_dir_node.File(sdk_components["full.bin"]), + UPDATE_BUNDLE_DIR=sdk_current_sdk_dir_node.Dir(sdk_components["update.dir"]), + SVD_FILE="${FBT_DEBUG_DIR}/STM32WB55_CM4.svd", + # Build variables + ROOT_DIR=env.Dir("#"), + FIRMWARE_BUILD_CFG="firmware", + TARGET_HW=int(sdk_data["hardware"]), + CFLAGS_APP=sdk_data["cc_args"], + CXXFLAGS_APP=sdk_data["cpp_args"], + LINKFLAGS_APP=sdk_data["linker_args"], + LIBS=sdk_data["linker_libs"], + # ufbt state + # UFBT_STATE_DIR=ufbt_state_dir_node, + # UFBT_CURRENT_SDK_DIR=sdk_current_sdk_dir_node, + UFBT_STATE=ufbt_state, + UFBT_BOOTSTRAP_SCRIPT="${UFBT_SCRIPT_DIR}/bootstrap.py", + UFBT_SCRIPT_ROOT=scripts_dir.Dir("ufbt"), + ) + + sys.path.insert(0, env["FBT_SCRIPT_DIR"].abspath) + + +def exists(env): + return True diff --git a/scripts/ufbt/update.scons b/scripts/ufbt/update.scons new file mode 100644 index 0000000000..9658e0bb20 --- /dev/null +++ b/scripts/ufbt/update.scons @@ -0,0 +1,37 @@ +from SCons.Errors import StopError + +Import("core_env") + +update_env = core_env.Clone( + toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")], + tools=["python3"], +) +print("Updating SDK...") +ufbt_state = update_env["UFBT_STATE"] + +update_args = [ + "--ufbt-dir", + f'"{update_env["UFBT_STATE_DIR"]}"', +] + +if branch_name := GetOption("sdk_branch"): + update_args.extend(["--branch", branch_name]) +elif channel_name := GetOption("sdk_channel"): + update_args.extend(["--channel", channel_name]) +elif branch_name := ufbt_state.get("branch", None): + update_args.extend(["--branch", branch_name]) +elif channel_name := ufbt_state.get("channel", None): + update_args.extend(["--channel", channel_name]) +else: + raise StopError("No branch or channel specified for SDK update") + +if hw_target := GetOption("sdk_target"): + update_args.extend(["--hw-target", hw_target]) +else: + update_args.extend(["--hw-target", ufbt_state["hw_target"]]) + +update_env.Replace(UPDATE_ARGS=update_args) +result = update_env.Execute( + update_env.subst('$PYTHON3 "$UFBT_BOOTSTRAP_SCRIPT" $UPDATE_ARGS'), +) +Exit(result) diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 208b757755..798b85ea10 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -112,86 +112,47 @@ Alias( extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], []) + if appsrc := appenv.subst("$APPSRC"): - deploy_sources, flipp_dist_paths, validators = [], [], [] - run_script_extra_ars = "" - - def _add_dist_targets(app_artifacts): - validators.append(app_artifacts.validator) - for _, ext_path in app_artifacts.dist_entries: - deploy_sources.append(app_artifacts.compact) - flipp_dist_paths.append(f"/ext/{ext_path}") - return app_artifacts - - def _add_host_app_to_targets(host_app): - artifacts_app_to_run = appenv["EXT_APPS"].get(host_app.appid, None) - _add_dist_targets(artifacts_app_to_run) - for plugin in host_app._plugins: - _add_dist_targets(appenv["EXT_APPS"].get(plugin.appid, None)) - - artifacts_app_to_run = appenv.GetExtAppByIdOrPath(appsrc) - if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN: - # We deploy host app instead - host_app = appenv["APPMGR"].get(artifacts_app_to_run.app.requires[0]) - - if host_app: - if host_app.apptype == FlipperAppType.EXTERNAL: - _add_host_app_to_targets(host_app) - else: - # host app is a built-in app - run_script_extra_ars = f"-a {host_app.name}" - _add_dist_targets(artifacts_app_to_run) - else: - raise UserError("Host app is unknown") - else: - _add_host_app_to_targets(artifacts_app_to_run.app) - - # print(deploy_sources, flipp_dist_paths) - appenv.PhonyTarget( - "launch_app", - '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', - source=deploy_sources, - FLIPPER_FILE_TARGETS=flipp_dist_paths, - EXTRA_ARGS=run_script_extra_ars, - ) - appenv.Alias("launch_app", validators) + appenv.AddAppLaunchTarget(appsrc, "launch_app") + # SDK management -sdk_origin_path = "${BUILD_DIR}/sdk_origin" -sdk_source = appenv.SDKPrebuilder( - sdk_origin_path, +amalgamated_api = "${BUILD_DIR}/sdk_origin" +sdk_source = appenv.ApiAmalgamator( + amalgamated_api, # Deps on root SDK headers and generated files (appenv["SDK_HEADERS"], appenv["FW_ASSETS_HEADERS"]), ) # Extra deps on headers included in deeper levels # Available on second and subsequent builds -Depends(sdk_source, appenv.ProcessSdkDepends(f"{sdk_origin_path}.d")) +Depends(sdk_source, appenv.ProcessSdkDepends(f"{amalgamated_api}.d")) -appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk") -sdk_tree = appenv.SDKTree(appenv["SDK_DIR"], sdk_origin_path) +appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk_headers") +sdk_header_tree = appenv.SDKHeaderTreeExtractor(appenv["SDK_DIR"], amalgamated_api) # AlwaysBuild(sdk_tree) -Alias("sdk_tree", sdk_tree) -extapps.sdk_tree = sdk_tree +Alias("sdk_tree", sdk_header_tree) +extapps.sdk_tree = sdk_header_tree -sdk_apicheck = appenv.SDKSymUpdater(appenv["SDK_DEFINITION"], sdk_origin_path) -Precious(sdk_apicheck) -NoClean(sdk_apicheck) -AlwaysBuild(sdk_apicheck) -Alias("sdk_check", sdk_apicheck) +api_check = appenv.ApiTableValidator(appenv["SDK_DEFINITION"], amalgamated_api) +Precious(api_check) +NoClean(api_check) +AlwaysBuild(api_check) +Alias("api_check", api_check) -sdk_apisyms = appenv.SDKSymGenerator( - "${BUILD_DIR}/assets/compiled/symbols.h", appenv["SDK_DEFINITION"] +firmware_apitable = appenv.ApiSymbolTable( + "${BUILD_DIR}/assets/compiled/firmware_api_table.h", appenv["SDK_DEFINITION"] ) -Alias("api_syms", sdk_apisyms) +Alias("api_table", firmware_apitable) ENV.Replace( - SDK_APISYMS=sdk_apisyms, + FW_API_TABLE=firmware_apitable, _APP_ICONS=appenv["_APP_ICONS"], ) if appenv["FORCE"]: - appenv.AlwaysBuild(sdk_source, sdk_tree, sdk_apicheck, sdk_apisyms) + appenv.AlwaysBuild(sdk_source, sdk_header_tree, api_check, firmware_apitable) Return("extapps") From b4ceb55fd20e66709311a652a336286839c5d672 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 6 Apr 2023 06:36:12 +0300 Subject: [PATCH 20/32] [FL-2524] Graphics cleanup and icon rotation (#2561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Canvas with rotation * Full icon rotation, cleanup of unused resources * F18 API update * Bitmap draw cleanup * More cleaning up * Migrate recovery and DFU to canvas * Make the internal draw function static * Remove all calls to u8g2_DrawXBM Co-authored-by: あく --- .../dap_link/gui/views/dap_main_view.c | 8 +- .../dap_link/icons/ArrowDownEmpty_12x18.png | Bin 160 -> 0 bytes .../dap_link/icons/ArrowDownFilled_12x18.png | Bin 168 -> 0 bytes applications/main/gpio/views/gpio_usb_uart.c | 4 +- applications/services/desktop/desktop.c | 4 +- .../desktop/views/desktop_view_pin_input.c | 13 +- applications/services/gui/canvas.c | 136 +++++++++++++++++- applications/services/gui/canvas.h | 32 +++++ applications/services/gui/canvas_i.h | 19 +++ assets/icons/GPIO/ArrowDownEmpty_14x15.png | Bin 654 -> 0 bytes assets/icons/GPIO/ArrowDownFilled_14x15.png | Bin 669 -> 0 bytes assets/icons/PIN/Pin_arrow_down_7x9.png | Bin 3607 -> 0 bytes assets/icons/PIN/Pin_arrow_left_9x7.png | Bin 3603 -> 0 bytes assets/icons/PIN/Pin_arrow_right_9x7.png | Bin 3602 -> 0 bytes assets/icons/PIN/Pin_back_full_40x8.png | Bin 3641 -> 0 bytes assets/icons/StatusBar/Lock_8x8.png | Bin 303 -> 0 bytes firmware/targets/f18/api_symbols.csv | 9 +- firmware/targets/f7/api_symbols.csv | 3 +- firmware/targets/f7/src/dfu.c | 31 ++-- firmware/targets/f7/src/recovery.c | 45 +++--- 20 files changed, 237 insertions(+), 67 deletions(-) delete mode 100644 applications/external/dap_link/icons/ArrowDownEmpty_12x18.png delete mode 100644 applications/external/dap_link/icons/ArrowDownFilled_12x18.png delete mode 100644 assets/icons/GPIO/ArrowDownEmpty_14x15.png delete mode 100644 assets/icons/GPIO/ArrowDownFilled_14x15.png delete mode 100644 assets/icons/PIN/Pin_arrow_down_7x9.png delete mode 100644 assets/icons/PIN/Pin_arrow_left_9x7.png delete mode 100644 assets/icons/PIN/Pin_arrow_right_9x7.png delete mode 100644 assets/icons/PIN/Pin_back_full_40x8.png delete mode 100644 assets/icons/StatusBar/Lock_8x8.png diff --git a/applications/external/dap_link/gui/views/dap_main_view.c b/applications/external/dap_link/gui/views/dap_main_view.c index c5c8f9dff0..f54c5e3d58 100644 --- a/applications/external/dap_link/gui/views/dap_main_view.c +++ b/applications/external/dap_link/gui/views/dap_main_view.c @@ -51,10 +51,10 @@ static void dap_main_view_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); if(model->dap_active) { canvas_draw_icon(canvas, 14, 16, &I_ArrowUpFilled_12x18); - canvas_draw_icon(canvas, 28, 16, &I_ArrowDownFilled_12x18); + canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpFilled_12x18, IconRotation180); } else { canvas_draw_icon(canvas, 14, 16, &I_ArrowUpEmpty_12x18); - canvas_draw_icon(canvas, 28, 16, &I_ArrowDownEmpty_12x18); + canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpEmpty_12x18, IconRotation180); } switch(model->mode) { @@ -76,9 +76,9 @@ static void dap_main_view_draw_callback(Canvas* canvas, void* _model) { } if(model->rx_active) { - canvas_draw_icon(canvas, 101, 16, &I_ArrowDownFilled_12x18); + canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpFilled_12x18, IconRotation180); } else { - canvas_draw_icon(canvas, 101, 16, &I_ArrowDownEmpty_12x18); + canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpEmpty_12x18, IconRotation180); } canvas_draw_str_aligned(canvas, 100, 38, AlignCenter, AlignTop, "UART"); diff --git a/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png b/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png deleted file mode 100644 index 6007f74ab0e89fc751f503a8a5e4e44021c0106c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eO!3HGrSK5O(jKx9jP7LeL$-D$|qC8z3Lo_D7 zo$SbWK!Jlr{PF+&Hnv;TJQ&v7?g&r!Fz9A{(LGbM+u3CI@thML?q)j=*|$VqJ$T$$ ztuvzCPsSl~7YFluKK0CllcIM`vf0BqQzUWaj&{}5`5zhLf9t51+o=lhbp(50(A=anC2ZSBvNHg_RnWm*uWGEIRgogS6BGwsp^g6qa`c R{s7v>;OXk;vd$@?2>@rkKMeo? diff --git a/applications/main/gpio/views/gpio_usb_uart.c b/applications/main/gpio/views/gpio_usb_uart.c index 837f2e3ec5..3234309a29 100644 --- a/applications/main/gpio/views/gpio_usb_uart.c +++ b/applications/main/gpio/views/gpio_usb_uart.c @@ -80,9 +80,9 @@ static void gpio_usb_uart_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, 48, 14, &I_ArrowUpEmpty_14x15); if(model->rx_active) - canvas_draw_icon(canvas, 48, 34, &I_ArrowDownFilled_14x15); + canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180); else - canvas_draw_icon(canvas, 48, 34, &I_ArrowDownEmpty_14x15); + canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpEmpty_14x15, IconRotation180); } static bool gpio_usb_uart_input_callback(InputEvent* event, void* context) { diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 556f423335..41470ed3a1 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -37,7 +37,7 @@ static void desktop_loader_callback(const void* message, void* context) { static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) { UNUSED(context); furi_assert(canvas); - canvas_draw_icon(canvas, 0, 0, &I_Lock_8x8); + canvas_draw_icon(canvas, 0, 0, &I_Lock_7x8); } static void desktop_dummy_mode_icon_draw_callback(Canvas* canvas, void* context) { @@ -230,7 +230,7 @@ Desktop* desktop_alloc() { // Lock icon desktop->lock_icon_viewport = view_port_alloc(); - view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_8x8)); + view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_7x8)); view_port_draw_callback_set( desktop->lock_icon_viewport, desktop_lock_icon_draw_callback, desktop); view_port_enabled_set(desktop->lock_icon_viewport, false); diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c index b86bf2929b..d3dadd7d71 100644 --- a/applications/services/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -115,16 +115,18 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu } else { switch(model->pin.data[i]) { case InputKeyDown: - canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_down_7x9); + canvas_draw_icon_ex( + canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9, IconRotation180); break; case InputKeyUp: - canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9); + canvas_draw_icon_ex(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9, IconRotation0); break; case InputKeyLeft: - canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_left_9x7); + canvas_draw_icon_ex( + canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation270); break; case InputKeyRight: - canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_right_9x7); + canvas_draw_icon_ex(canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation90); break; default: furi_assert(0); @@ -147,7 +149,8 @@ static void desktop_view_pin_input_draw(Canvas* canvas, void* context) { desktop_view_pin_input_draw_cells(canvas, model); if((model->pin.length > 0) && !model->locked_input) { - canvas_draw_icon(canvas, 4, 53, &I_Pin_back_full_40x8); + canvas_draw_icon(canvas, 4, 53, &I_Pin_back_arrow_10x8); + canvas_draw_str(canvas, 16, 60, "= clear"); } if(model->button_label && ((model->pin.length >= MIN_PIN_SIZE) || model->locked_input)) { diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 40797c0867..6e35dcffb0 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -221,7 +221,7 @@ void canvas_draw_bitmap( y += canvas->offset_y; uint8_t* bitmap_data = NULL; compress_icon_decode(canvas->compress_icon, compressed_bitmap_data, &bitmap_data); - u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data); + canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap_data, IconRotation0); } void canvas_draw_icon_animation( @@ -237,13 +237,138 @@ void canvas_draw_icon_animation( uint8_t* icon_data = NULL; compress_icon_decode( canvas->compress_icon, icon_animation_get_data(icon_animation), &icon_data); - u8g2_DrawXBM( + canvas_draw_u8g2_bitmap( &canvas->fb, x, y, icon_animation_get_width(icon_animation), icon_animation_get_height(icon_animation), - icon_data); + icon_data, + IconRotation0); +} + +static void canvas_draw_u8g2_bitmap_int( + u8g2_t* u8g2, + u8g2_uint_t x, + u8g2_uint_t y, + u8g2_uint_t w, + u8g2_uint_t h, + bool mirror, + bool rotation, + const uint8_t* bitmap) { + u8g2_uint_t blen; + blen = w; + blen += 7; + blen >>= 3; + + if(rotation && !mirror) { + x += w + 1; + } else if(mirror && !rotation) { + y += h - 1; + } + + while(h > 0) { + const uint8_t* b = bitmap; + uint16_t len = w; + uint16_t x0 = x; + uint16_t y0 = y; + uint8_t mask; + uint8_t color = u8g2->draw_color; + uint8_t ncolor = (color == 0 ? 1 : 0); + mask = 1; + + while(len > 0) { + if(u8x8_pgm_read(b) & mask) { + u8g2->draw_color = color; + u8g2_DrawHVLine(u8g2, x0, y0, 1, 0); + } else if(u8g2->bitmap_transparency == 0) { + u8g2->draw_color = ncolor; + u8g2_DrawHVLine(u8g2, x0, y0, 1, 0); + } + + if(rotation) { + y0++; + } else { + x0++; + } + + mask <<= 1; + if(mask == 0) { + mask = 1; + b++; + } + len--; + } + + u8g2->draw_color = color; + bitmap += blen; + + if(mirror) { + if(rotation) { + x++; + } else { + y--; + } + } else { + if(rotation) { + x--; + } else { + y++; + } + } + h--; + } +} + +void canvas_draw_u8g2_bitmap( + u8g2_t* u8g2, + u8g2_uint_t x, + u8g2_uint_t y, + u8g2_uint_t w, + u8g2_uint_t h, + const uint8_t* bitmap, + IconRotation rotation) { + u8g2_uint_t blen; + blen = w; + blen += 7; + blen >>= 3; +#ifdef U8G2_WITH_INTERSECTION + if(u8g2_IsIntersection(u8g2, x, y, x + w, y + h) == 0) return; +#endif /* U8G2_WITH_INTERSECTION */ + + switch(rotation) { + case IconRotation0: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 0, 0, bitmap); + break; + case IconRotation90: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 0, 1, bitmap); + break; + case IconRotation180: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 1, 0, bitmap); + break; + case IconRotation270: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 1, 1, bitmap); + break; + default: + break; + } +} + +void canvas_draw_icon_ex( + Canvas* canvas, + uint8_t x, + uint8_t y, + const Icon* icon, + IconRotation rotation) { + furi_assert(canvas); + furi_assert(icon); + + x += canvas->offset_x; + y += canvas->offset_y; + uint8_t* icon_data = NULL; + compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); + canvas_draw_u8g2_bitmap( + &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, rotation); } void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) { @@ -254,7 +379,8 @@ void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) { y += canvas->offset_y; uint8_t* icon_data = NULL; compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); - u8g2_DrawXBM(&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data); + canvas_draw_u8g2_bitmap( + &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, IconRotation0); } void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y) { @@ -364,7 +490,7 @@ void canvas_draw_xbm( furi_assert(canvas); x += canvas->offset_x; y += canvas->offset_y; - u8g2_DrawXBM(&canvas->fb, x, y, w, h, bitmap); + canvas_draw_u8g2_bitmap(&canvas->fb, x, y, w, h, bitmap, IconRotation0); } void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch) { diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index f8fe2c1db5..f34d02bfba 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -64,6 +64,22 @@ typedef struct { uint8_t descender; } CanvasFontParameters; +/** Icon flip */ +typedef enum { + IconFlipNone, + IconFlipHorizontal, + IconFlipVertical, + IconFlipBoth, +} IconFlip; + +/** Icon rotation */ +typedef enum { + IconRotation0, + IconRotation90, + IconRotation180, + IconRotation270, +} IconRotation; + /** Canvas anonymous structure */ typedef struct Canvas Canvas; @@ -217,6 +233,22 @@ void canvas_draw_bitmap( uint8_t height, const uint8_t* compressed_bitmap_data); +/** Draw icon at position defined by x,y with rotation and flip. + * + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param icon Icon instance + * @param flip IconFlip + * @param rotation IconRotation + */ +void canvas_draw_icon_ex( + Canvas* canvas, + uint8_t x, + uint8_t y, + const Icon* icon, + IconRotation rotation); + /** Draw animation at position defined by x,y. * * @param canvas Canvas instance diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index 39e7021bc9..f3b8f17ad7 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -78,3 +78,22 @@ void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation); * @return CanvasOrientation */ CanvasOrientation canvas_get_orientation(const Canvas* canvas); + +/** Draw a u8g2 bitmap + * + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param width width + * @param height height + * @param bitmap bitmap + * @param rotation rotation + */ +void canvas_draw_u8g2_bitmap( + u8g2_t* u8g2, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + const uint8_t* bitmap, + uint8_t rotation); diff --git a/assets/icons/GPIO/ArrowDownEmpty_14x15.png b/assets/icons/GPIO/ArrowDownEmpty_14x15.png deleted file mode 100644 index 8c6d54f9cbee1fa08f30d7d8d309fcf0eae24dc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 654 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 QX&eA7&1_6djNPvM5BL~PlmGw# diff --git a/assets/icons/PIN/Pin_arrow_left_9x7.png b/assets/icons/PIN/Pin_arrow_left_9x7.png deleted file mode 100644 index fb4ded78fde8d1bf4f053ba0b353e7acf14f031a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3603 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 diff --git a/assets/icons/PIN/Pin_back_full_40x8.png b/assets/icons/PIN/Pin_back_full_40x8.png deleted file mode 100644 index cd1301512db1c06700fec26d03231c63b9be75d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3641 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 Cv0zgG diff --git a/assets/icons/StatusBar/Lock_8x8.png b/assets/icons/StatusBar/Lock_8x8.png deleted file mode 100644 index 01fb0eb6bf7506d19f5c6e039df0ef0d3272e424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 303 zcmeAS@N?(olHy`uVBq!ia0vp^93afZ3?z3ZhDiV^mUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5l;8~T332`ZAIM?=k_T^V zeFCx=OM?7@862M7NCR<_yxm zF*LUVA`?Sx0|P4qgWh8dPf;}F=BH$)Rbpx|Hi2kZk~Z%-Py>UftDnm{r-UW|;$Ka1 diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 40e23a747f..7713beb93b 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,20.0,, +Version,+,20.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -539,6 +539,7 @@ Function,+,canvas_draw_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t" Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*" Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*" +Function,+,canvas_draw_icon_ex,void,"Canvas*, uint8_t, uint8_t, const Icon*, IconRotation" Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" @@ -875,12 +876,6 @@ Function,-,furi_hal_clock_resume_tick,void, Function,-,furi_hal_clock_suspend_tick,void, Function,-,furi_hal_clock_switch_to_hsi,void, Function,-,furi_hal_clock_switch_to_pll,void, -Function,-,compress_alloc,Compress*,uint16_t -Function,-,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,compress_free,void,Compress* -Function,-,compress_icon_decode,void,"const uint8_t*, uint8_t**" -Function,-,compress_icon_init,void, Function,+,furi_hal_console_disable,void, Function,+,furi_hal_console_enable,void, Function,+,furi_hal_console_init,void, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8b1d29b1ce..fea7926801 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,20.0,, +Version,+,20.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -621,6 +621,7 @@ Function,+,canvas_draw_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t" Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*" Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*" +Function,+,canvas_draw_icon_ex,void,"Canvas*, uint8_t, uint8_t, const Icon*, IconRotation" Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" diff --git a/firmware/targets/f7/src/dfu.c b/firmware/targets/f7/src/dfu.c index b060bc8d26..7e094b4c47 100644 --- a/firmware/targets/f7/src/dfu.c +++ b/firmware/targets/f7/src/dfu.c @@ -2,29 +2,24 @@ #include #include #include -#include #include #include +#include +#include void flipper_boot_dfu_show_splash() { // Initialize - CompressIcon* compress_icon = compress_icon_alloc(); - - u8g2_t* fb = malloc(sizeof(u8g2_t)); - memset(fb, 0, sizeof(u8g2_t)); - u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); - u8g2_InitDisplay(fb); - u8g2_SetDrawColor(fb, 0x01); - uint8_t* splash_data = NULL; - compress_icon_decode(compress_icon, icon_get_data(&I_DFU_128x50), &splash_data); - u8g2_DrawXBM(fb, 0, 64 - 50, 128, 50, splash_data); - u8g2_SetFont(fb, u8g2_font_helvB08_tr); - u8g2_DrawStr(fb, 2, 8, "Update & Recovery Mode"); - u8g2_DrawStr(fb, 2, 21, "DFU Started"); - u8g2_SetPowerSave(fb, 0); - u8g2_SendBuffer(fb); - - compress_icon_free(compress_icon); + Canvas* canvas = canvas_init(); + + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + + canvas_draw_icon(canvas, 0, 64 - 50, &I_DFU_128x50); + canvas_draw_str(canvas, 2, 8, "Update & Recovery Mode"); + canvas_draw_str(canvas, 2, 21, "DFU Started"); + canvas_commit(canvas); + + canvas_free(canvas); } void flipper_boot_dfu_exec() { diff --git a/firmware/targets/f7/src/recovery.c b/firmware/targets/f7/src/recovery.c index d037e8118f..49d780d478 100644 --- a/firmware/targets/f7/src/recovery.c +++ b/firmware/targets/f7/src/recovery.c @@ -2,44 +2,43 @@ #include #include #include -#include #include #include +#include +#include #define COUNTER_VALUE (136U) -static void flipper_boot_recovery_draw_splash(u8g2_t* fb, size_t progress) { +static void flipper_boot_recovery_draw_progress(Canvas* canvas, size_t progress) { if(progress < COUNTER_VALUE) { // Fill the progress bar while the progress is going down - u8g2_SetDrawColor(fb, 0x01); - u8g2_DrawRFrame(fb, 59, 41, 69, 8, 2); + canvas_draw_rframe(canvas, 59, 41, 69, 8, 2); size_t width = (COUNTER_VALUE - progress) * 68 / COUNTER_VALUE; - u8g2_DrawBox(fb, 60, 42, width, 6); + canvas_draw_box(canvas, 60, 42, width, 6); } else { - u8g2_SetDrawColor(fb, 0x00); - u8g2_DrawRBox(fb, 59, 41, 69, 8, 2); + canvas_draw_rframe(canvas, 59, 41, 69, 8, 2); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 60, 42, 67, 6); + canvas_set_color(canvas, ColorBlack); } - u8g2_SendBuffer(fb); + canvas_commit(canvas); } -void flipper_boot_recovery_exec() { - u8g2_t* fb = malloc(sizeof(u8g2_t)); - u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); - u8g2_InitDisplay(fb); +void flipper_boot_recovery_draw_splash(Canvas* canvas) { + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + + canvas_draw_icon(canvas, 0, 0, &I_Erase_pin_128x64); - CompressIcon* compress_icon = compress_icon_alloc(); - uint8_t* splash_data = NULL; - compress_icon_decode(compress_icon, icon_get_data(&I_Erase_pin_128x64), &splash_data); + canvas_commit(canvas); +} - u8g2_ClearBuffer(fb); - u8g2_SetDrawColor(fb, 0x01); +void flipper_boot_recovery_exec() { + Canvas* canvas = canvas_init(); - // Draw the recovery picture - u8g2_DrawXBM(fb, 0, 0, 128, 64, splash_data); - u8g2_SendBuffer(fb); - u8g2_SetPowerSave(fb, 0); - compress_icon_free(compress_icon); + // Show recovery splashscreen + flipper_boot_recovery_draw_splash(canvas); size_t counter = COUNTER_VALUE; while(counter) { @@ -53,7 +52,7 @@ void flipper_boot_recovery_exec() { counter = COUNTER_VALUE; } - flipper_boot_recovery_draw_splash(fb, counter); + flipper_boot_recovery_draw_progress(canvas, counter); } if(!counter) { From d1ad9242165143215ec9267914d23e58f837374c Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 6 Apr 2023 08:13:30 +0400 Subject: [PATCH 21/32] [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 --- .../avr_isp_programmer/application.fam | 17 + .../avr_isp_programmer/avr_app_icon_10x10.png | Bin 0 -> 3614 bytes .../external/avr_isp_programmer/avr_isp_app.c | 179 +++ .../avr_isp_programmer/avr_isp_app_i.c | 31 + .../avr_isp_programmer/avr_isp_app_i.h | 44 + .../avr_isp_programmer/helpers/avr_isp.c | 490 +++++++ .../avr_isp_programmer/helpers/avr_isp.h | 70 + .../helpers/avr_isp_event.h | 23 + .../helpers/avr_isp_types.h | 32 + .../helpers/avr_isp_worker.c | 266 ++++ .../helpers/avr_isp_worker.h | 49 + .../helpers/avr_isp_worker_rw.c | 1145 +++++++++++++++++ .../helpers/avr_isp_worker_rw.h | 99 ++ .../helpers/flipper_i32hex_file.c | 321 +++++ .../helpers/flipper_i32hex_file.h | 55 + .../images/avr_app_icon_10x10.png | Bin 0 -> 3614 bytes .../avr_isp_programmer/images/avr_wiring.png | Bin 0 -> 4513 bytes .../images/chif_not_found_83x37.png | Bin 0 -> 3742 bytes .../images/chip_error_70x22.png | Bin 0 -> 3688 bytes .../images/chip_long_70x22.png | Bin 0 -> 3656 bytes .../images/chip_not_found_83x37.png | Bin 0 -> 3779 bytes .../images/dolphin_nice_96x59.png | Bin 0 -> 2459 bytes .../images/isp_active_128x53.png | Bin 0 -> 3961 bytes .../images/link_waiting_77x56.png | Bin 0 -> 3883 bytes .../lib/driver/avr_isp_chip_arr.c | 386 ++++++ .../lib/driver/avr_isp_chip_arr.h | 33 + .../lib/driver/avr_isp_prog.c | 633 +++++++++ .../lib/driver/avr_isp_prog.h | 16 + .../lib/driver/avr_isp_prog_cmd.h | 97 ++ .../lib/driver/avr_isp_spi_sw.c | 73 ++ .../lib/driver/avr_isp_spi_sw.h | 24 + .../avr_isp_programmer/lib/driver/clock.png | Bin 0 -> 3649 bytes .../avr_isp_programmer/scenes/avr_isp_scene.c | 30 + .../avr_isp_programmer/scenes/avr_isp_scene.h | 29 + .../scenes/avr_isp_scene_about.c | 99 ++ .../scenes/avr_isp_scene_chip_detect.c | 72 ++ .../scenes/avr_isp_scene_config.h | 10 + .../scenes/avr_isp_scene_input_name.c | 89 ++ .../scenes/avr_isp_scene_load.c | 22 + .../scenes/avr_isp_scene_programmer.c | 28 + .../scenes/avr_isp_scene_reader.c | 64 + .../scenes/avr_isp_scene_start.c | 75 ++ .../scenes/avr_isp_scene_success.c | 44 + .../scenes/avr_isp_scene_wiring.c | 21 + .../scenes/avr_isp_scene_writer.c | 69 + .../views/avr_isp_view_chip_detect.c | 213 +++ .../views/avr_isp_view_chip_detect.h | 32 + .../views/avr_isp_view_programmer.c | 134 ++ .../views/avr_isp_view_programmer.h | 27 + .../views/avr_isp_view_reader.c | 215 ++++ .../views/avr_isp_view_reader.h | 35 + .../views/avr_isp_view_writer.c | 268 ++++ .../views/avr_isp_view_writer.h | 37 + firmware/targets/f18/api_symbols.csv | 6 + firmware/targets/f7/api_symbols.csv | 6 + lib/toolbox/SConscript | 1 + 56 files changed, 5709 insertions(+) create mode 100644 applications/external/avr_isp_programmer/application.fam create mode 100644 applications/external/avr_isp_programmer/avr_app_icon_10x10.png create mode 100644 applications/external/avr_isp_programmer/avr_isp_app.c create mode 100644 applications/external/avr_isp_programmer/avr_isp_app_i.c create mode 100644 applications/external/avr_isp_programmer/avr_isp_app_i.h create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp.c create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp.h create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_event.h create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_types.h create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker.c create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker.h create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h create mode 100644 applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c create mode 100644 applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h create mode 100644 applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png create mode 100644 applications/external/avr_isp_programmer/images/avr_wiring.png create mode 100644 applications/external/avr_isp_programmer/images/chif_not_found_83x37.png create mode 100644 applications/external/avr_isp_programmer/images/chip_error_70x22.png create mode 100644 applications/external/avr_isp_programmer/images/chip_long_70x22.png create mode 100644 applications/external/avr_isp_programmer/images/chip_not_found_83x37.png create mode 100644 applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png create mode 100644 applications/external/avr_isp_programmer/images/isp_active_128x53.png create mode 100644 applications/external/avr_isp_programmer/images/link_waiting_77x56.png create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.c create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h create mode 100644 applications/external/avr_isp_programmer/lib/driver/clock.png create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene.h create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_reader.c create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_reader.h create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_writer.c create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_writer.h diff --git a/applications/external/avr_isp_programmer/application.fam b/applications/external/avr_isp_programmer/application.fam new file mode 100644 index 0000000000..19556d03d3 --- /dev/null +++ b/applications/external/avr_isp_programmer/application.fam @@ -0,0 +1,17 @@ +App( + appid="avr_isp", + name="AVR Flasher", + apptype=FlipperAppType.EXTERNAL, + entry_point="avr_isp_app", + requires=["gui"], + stack_size=4 * 1024, + order=20, + fap_icon="avr_app_icon_10x10.png", + fap_category="GPIO", + fap_icon_assets="images", + fap_private_libs=[ + Lib( + name="driver", + ), + ], +) diff --git a/applications/external/avr_isp_programmer/avr_app_icon_10x10.png b/applications/external/avr_isp_programmer/avr_app_icon_10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..533787fe3569f70196d3f71e8373102dfd3967a0 GIT binary patch literal 3614 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 Vpqb^9w@m;7?5&;gRaoD1{{|C}E&c!i literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/avr_isp_app.c b/applications/external/avr_isp_programmer/avr_isp_app.c new file mode 100644 index 0000000000..740dc3610a --- /dev/null +++ b/applications/external/avr_isp_programmer/avr_isp_app.c @@ -0,0 +1,179 @@ +#include "avr_isp_app_i.h" + +static bool avr_isp_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + AvrIspApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool avr_isp_app_back_event_callback(void* context) { + furi_assert(context); + AvrIspApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void avr_isp_app_tick_event_callback(void* context) { + furi_assert(context); + AvrIspApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +AvrIspApp* avr_isp_app_alloc() { + AvrIspApp* app = malloc(sizeof(AvrIspApp)); + + app->file_path = furi_string_alloc(); + furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX); + app->error = AvrIspErrorNoError; + + // GUI + app->gui = furi_record_open(RECORD_GUI); + + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&avr_isp_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, avr_isp_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, avr_isp_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, avr_isp_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, AvrIspViewSubmenu, submenu_get_view(app->submenu)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view(app->view_dispatcher, AvrIspViewWidget, widget_get_view(app->widget)); + + // Text Input + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, AvrIspViewTextInput, text_input_get_view(app->text_input)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, AvrIspViewPopup, popup_get_view(app->popup)); + + //Dialog + app->dialogs = furi_record_open(RECORD_DIALOGS); + + // Programmer view + app->avr_isp_programmer_view = avr_isp_programmer_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + AvrIspViewProgrammer, + avr_isp_programmer_view_get_view(app->avr_isp_programmer_view)); + + // Reader view + app->avr_isp_reader_view = avr_isp_reader_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + AvrIspViewReader, + avr_isp_reader_view_get_view(app->avr_isp_reader_view)); + + // Writer view + app->avr_isp_writer_view = avr_isp_writer_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + AvrIspViewWriter, + avr_isp_writer_view_get_view(app->avr_isp_writer_view)); + + // Chip detect view + app->avr_isp_chip_detect_view = avr_isp_chip_detect_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + AvrIspViewChipDetect, + avr_isp_chip_detect_view_get_view(app->avr_isp_chip_detect_view)); + + // Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + furi_delay_ms(10); + } + + scene_manager_next_scene(app->scene_manager, AvrIspSceneStart); + + return app; +} //-V773 + +void avr_isp_app_free(AvrIspApp* app) { + furi_assert(app); + + // Disable 5v power + if(furi_hal_power_is_otg_enabled()) { + furi_hal_power_disable_otg(); + } + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewSubmenu); + submenu_free(app->submenu); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWidget); + widget_free(app->widget); + + // TextInput + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewTextInput); + text_input_free(app->text_input); + + // Popup + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewPopup); + popup_free(app->popup); + + //Dialog + furi_record_close(RECORD_DIALOGS); + + // Programmer view + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewProgrammer); + avr_isp_programmer_view_free(app->avr_isp_programmer_view); + + // Reader view + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewReader); + avr_isp_reader_view_free(app->avr_isp_reader_view); + + // Writer view + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWriter); + avr_isp_writer_view_free(app->avr_isp_writer_view); + + // Chip detect view + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewChipDetect); + avr_isp_chip_detect_view_free(app->avr_isp_chip_detect_view); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + + // Close records + furi_record_close(RECORD_GUI); + + // Path strings + furi_string_free(app->file_path); + + free(app); +} + +int32_t avr_isp_app(void* p) { + UNUSED(p); + AvrIspApp* avr_isp_app = avr_isp_app_alloc(); + + view_dispatcher_run(avr_isp_app->view_dispatcher); + + avr_isp_app_free(avr_isp_app); + + return 0; +} diff --git a/applications/external/avr_isp_programmer/avr_isp_app_i.c b/applications/external/avr_isp_programmer/avr_isp_app_i.c new file mode 100644 index 0000000000..7a7fa6d7f1 --- /dev/null +++ b/applications/external/avr_isp_programmer/avr_isp_app_i.c @@ -0,0 +1,31 @@ +#include "avr_isp_app_i.h" +#include +#include + +#define TAG "AvrIsp" + +bool avr_isp_load_from_file(AvrIspApp* app) { + furi_assert(app); + + FuriString* file_path = furi_string_alloc(); + FuriString* file_name = furi_string_alloc(); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, AVR_ISP_APP_EXTENSION, &I_avr_app_icon_10x10); + browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; + + // Input events and views are managed by file_select + bool res = dialog_file_browser_show(app->dialogs, file_path, app->file_path, &browser_options); + + if(res) { + path_extract_dirname(furi_string_get_cstr(file_path), app->file_path); + path_extract_filename(file_path, file_name, true); + strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME); + } + + furi_string_free(file_name); + furi_string_free(file_path); + + return res; +} diff --git a/applications/external/avr_isp_programmer/avr_isp_app_i.h b/applications/external/avr_isp_programmer/avr_isp_app_i.h new file mode 100644 index 0000000000..17c69f8f2e --- /dev/null +++ b/applications/external/avr_isp_programmer/avr_isp_app_i.h @@ -0,0 +1,44 @@ +#pragma once + +#include "helpers/avr_isp_types.h" +#include + +#include "scenes/avr_isp_scene.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "views/avr_isp_view_programmer.h" +#include "views/avr_isp_view_reader.h" +#include "views/avr_isp_view_writer.h" +#include "views/avr_isp_view_chip_detect.h" + +#define AVR_ISP_MAX_LEN_NAME 64 + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Popup* popup; + Submenu* submenu; + Widget* widget; + TextInput* text_input; + FuriString* file_path; + char file_name_tmp[AVR_ISP_MAX_LEN_NAME]; + AvrIspProgrammerView* avr_isp_programmer_view; + AvrIspReaderView* avr_isp_reader_view; + AvrIspWriterView* avr_isp_writer_view; + AvrIspChipDetectView* avr_isp_chip_detect_view; + AvrIspError error; +} AvrIspApp; + +bool avr_isp_load_from_file(AvrIspApp* app); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.c b/applications/external/avr_isp_programmer/helpers/avr_isp.c new file mode 100644 index 0000000000..76e0a80b06 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp.c @@ -0,0 +1,490 @@ +#include "avr_isp.h" +#include "../lib/driver/avr_isp_prog_cmd.h" +#include "../lib/driver/avr_isp_spi_sw.h" + +#include + +#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320 +#define TAG "AvrIsp" + +struct AvrIsp { + AvrIspSpiSw* spi; + bool pmode; + AvrIspCallback callback; + void* context; +}; + +AvrIsp* avr_isp_alloc(void) { + AvrIsp* instance = malloc(sizeof(AvrIsp)); + return instance; +} + +void avr_isp_free(AvrIsp* instance) { + furi_assert(instance); + + if(instance->spi) avr_isp_end_pmode(instance); + free(instance); +} + +void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context) { + furi_assert(instance); + furi_assert(context); + + instance->callback = callback; + instance->context = context; +} + +uint8_t avr_isp_spi_transaction( + AvrIsp* instance, + uint8_t cmd, + uint8_t addr_hi, + uint8_t addr_lo, + uint8_t data) { + furi_assert(instance); + + avr_isp_spi_sw_txrx(instance->spi, cmd); + avr_isp_spi_sw_txrx(instance->spi, addr_hi); + avr_isp_spi_sw_txrx(instance->spi, addr_lo); + return avr_isp_spi_sw_txrx(instance->spi, data); +} + +static bool avr_isp_set_pmode(AvrIsp* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + furi_assert(instance); + + uint8_t res = 0; + avr_isp_spi_sw_txrx(instance->spi, a); + avr_isp_spi_sw_txrx(instance->spi, b); + res = avr_isp_spi_sw_txrx(instance->spi, c); + avr_isp_spi_sw_txrx(instance->spi, d); + return res == 0x53; +} + +void avr_isp_end_pmode(AvrIsp* instance) { + furi_assert(instance); + + if(instance->pmode) { + avr_isp_spi_sw_res_set(instance->spi, true); + // We're about to take the target out of reset + // so configure SPI pins as input + if(instance->spi) avr_isp_spi_sw_free(instance->spi); + instance->spi = NULL; + } + + instance->pmode = false; +} + +static bool avr_isp_start_pmode(AvrIsp* instance, AvrIspSpiSwSpeed spi_speed) { + furi_assert(instance); + + // Reset target before driving PIN_SCK or PIN_MOSI + + // SPI.begin() will configure SS as output, + // so SPI master mode is selected. + // We have defined RESET as pin 10, + // which for many arduino's is not the SS pin. + // So we have to configure RESET as output here, + // (reset_target() first sets the correct level) + if(instance->spi) avr_isp_spi_sw_free(instance->spi); + instance->spi = avr_isp_spi_sw_init(spi_speed); + + avr_isp_spi_sw_res_set(instance->spi, false); + // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm": + + // Pulse RESET after PIN_SCK is low: + avr_isp_spi_sw_sck_set(instance->spi, false); + + // discharge PIN_SCK, value arbitrally chosen + furi_delay_ms(20); + avr_isp_spi_sw_res_set(instance->spi, true); + + // Pulse must be minimum 2 target CPU speed cycles + // so 100 usec is ok for CPU speeds above 20KHz + furi_delay_ms(1); + + avr_isp_spi_sw_res_set(instance->spi, false); + + // Send the enable programming command: + // datasheet: must be > 20 msec + furi_delay_ms(50); + if(avr_isp_set_pmode(instance, AVR_ISP_SET_PMODE)) { + instance->pmode = true; + return true; + } + return false; +} + +bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance) { + furi_assert(instance); + + AvrIspSpiSwSpeed spi_speed[] = { + AvrIspSpiSwSpeed1Mhz, + AvrIspSpiSwSpeed400Khz, + AvrIspSpiSwSpeed250Khz, + AvrIspSpiSwSpeed125Khz, + AvrIspSpiSwSpeed60Khz, + AvrIspSpiSwSpeed40Khz, + AvrIspSpiSwSpeed20Khz, + AvrIspSpiSwSpeed10Khz, + AvrIspSpiSwSpeed5Khz, + AvrIspSpiSwSpeed1Khz, + }; + for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) { + if(avr_isp_start_pmode(instance, spi_speed[i])) { + AvrIspSignature sig = avr_isp_read_signature(instance); + AvrIspSignature sig_examination = avr_isp_read_signature(instance); //-V656 + uint8_t y = 0; + while(y < 8) { + if(memcmp((uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspSignature)) != + 0) + break; + sig_examination = avr_isp_read_signature(instance); + y++; + } + if(y == 8) { + if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) { + if(i < (COUNT_OF(spi_speed) - 1)) { + avr_isp_end_pmode(instance); + i++; + return avr_isp_start_pmode(instance, spi_speed[i]); + } + } + return true; + } + } + } + return false; +} + +static void avr_isp_commit(AvrIsp* instance, uint16_t addr, uint8_t data) { + furi_assert(instance); + + avr_isp_spi_transaction(instance, AVR_ISP_COMMIT(addr)); + /* polling flash */ + if(data == 0xFF) { + furi_delay_ms(5); + } else { + /* polling flash */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) { + break; + }; + } + } +} + +static uint16_t avr_isp_current_page(AvrIsp* instance, uint32_t addr, uint16_t page_size) { + furi_assert(instance); + + uint16_t page = 0; + switch(page_size) { + case 32: + page = addr & 0xFFFFFFF0; + break; + case 64: + page = addr & 0xFFFFFFE0; + break; + case 128: + page = addr & 0xFFFFFFC0; + break; + case 256: + page = addr & 0xFFFFFF80; + break; + + default: + page = addr; + break; + } + + return page; +} + +static bool avr_isp_flash_write_pages( + AvrIsp* instance, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + + size_t x = 0; + uint16_t page = avr_isp_current_page(instance, addr, page_size); + + while(x < data_size) { + if(page != avr_isp_current_page(instance, addr, page_size)) { + avr_isp_commit(instance, page, data[x - 1]); + page = avr_isp_current_page(instance, addr, page_size); + } + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_LO(addr, data[x++])); + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_HI(addr, data[x++])); + addr++; + } + avr_isp_commit(instance, page, data[x - 1]); + return true; +} + +bool avr_isp_erase_chip(AvrIsp* instance) { + furi_assert(instance); + + bool ret = false; + if(!instance->pmode) avr_isp_auto_set_spi_speed_start_pmode(instance); + if(instance->pmode) { + avr_isp_spi_transaction(instance, AVR_ISP_ERASE_CHIP); + furi_delay_ms(100); + avr_isp_end_pmode(instance); + ret = true; + } + return ret; +} + +static bool + avr_isp_eeprom_write(AvrIsp* instance, uint16_t addr, uint8_t* data, uint32_t data_size) { + furi_assert(instance); + + for(uint16_t i = 0; i < data_size; i++) { + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, data[i])); + furi_delay_ms(10); + addr++; + } + return true; +} + +bool avr_isp_write_page( + AvrIsp* instance, + uint32_t mem_type, + uint32_t mem_size, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + + bool ret = false; + switch(mem_type) { + case STK_SET_FLASH_TYPE: + if((addr + data_size / 2) <= mem_size) { + ret = avr_isp_flash_write_pages(instance, addr, page_size, data, data_size); + } + break; + + case STK_SET_EEPROM_TYPE: + if((addr + data_size) <= mem_size) { + ret = avr_isp_eeprom_write(instance, addr, data, data_size); + } + break; + + default: + furi_crash(TAG " Incorrect mem type."); + break; + } + + return ret; +} + +static bool avr_isp_flash_read_page( + AvrIsp* instance, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + + if(page_size > data_size) return false; + for(uint16_t i = 0; i < page_size; i += 2) { + data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(addr)); + data[i + 1] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)); + addr++; + } + return true; +} + +static bool avr_isp_eeprom_read_page( + AvrIsp* instance, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + + if(page_size > data_size) return false; + for(uint16_t i = 0; i < page_size; i++) { + data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr)); + addr++; + } + return true; +} + +bool avr_isp_read_page( + AvrIsp* instance, + uint32_t mem_type, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + + bool res = false; + if(mem_type == STK_SET_FLASH_TYPE) + res = avr_isp_flash_read_page(instance, addr, page_size, data, data_size); + if(mem_type == STK_SET_EEPROM_TYPE) + res = avr_isp_eeprom_read_page(instance, addr, page_size, data, data_size); + + return res; +} + +AvrIspSignature avr_isp_read_signature(AvrIsp* instance) { + furi_assert(instance); + + AvrIspSignature signature; + signature.vendor = avr_isp_spi_transaction(instance, AVR_ISP_READ_VENDOR); + signature.part_family = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY); + signature.part_number = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER); + return signature; +} + +uint8_t avr_isp_read_lock_byte(AvrIsp* instance) { + furi_assert(instance); + + uint8_t data = 0; + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 300) { + data = avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE); + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == data) { + break; + }; + data = 0x00; + } + return data; +} + +bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock) { + furi_assert(instance); + + bool ret = false; + if(avr_isp_read_lock_byte(instance) == lock) { + ret = true; + } else { + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_LOCK_BYTE(lock)); + /* polling lock byte */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == lock) { + ret = true; + break; + }; + } + } + return ret; +} + +uint8_t avr_isp_read_fuse_low(AvrIsp* instance) { + furi_assert(instance); + + uint8_t data = 0; + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 300) { + data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW); + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == data) { + break; + }; + data = 0x00; + } + return data; +} + +bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse) { + furi_assert(instance); + + bool ret = false; + if(avr_isp_read_fuse_low(instance) == lfuse) { + ret = true; + } else { + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_LOW(lfuse)); + /* polling fuse */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == lfuse) { + ret = true; + break; + }; + } + } + return ret; +} + +uint8_t avr_isp_read_fuse_high(AvrIsp* instance) { + furi_assert(instance); + + uint8_t data = 0; + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 300) { + data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH); + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == data) { + break; + }; + data = 0x00; + } + return data; +} + +bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse) { + furi_assert(instance); + + bool ret = false; + if(avr_isp_read_fuse_high(instance) == hfuse) { + ret = true; + } else { + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_HIGH(hfuse)); + /* polling fuse */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == hfuse) { + ret = true; + break; + }; + } + } + return ret; +} + +uint8_t avr_isp_read_fuse_extended(AvrIsp* instance) { + furi_assert(instance); + + uint8_t data = 0; + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 300) { + data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED); + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == data) { + break; + }; + data = 0x00; + } + return data; +} + +bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse) { + furi_assert(instance); + + bool ret = false; + if(avr_isp_read_fuse_extended(instance) == efuse) { + ret = true; + } else { + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_EXTENDED(efuse)); + /* polling fuse */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == efuse) { + ret = true; + break; + }; + } + } + return ret; +} + +void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr) { + furi_assert(instance); + + avr_isp_spi_transaction(instance, AVR_ISP_EXTENDED_ADDR(extended_addr)); + furi_delay_ms(10); +} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.h b/applications/external/avr_isp_programmer/helpers/avr_isp.h new file mode 100644 index 0000000000..476fc3d642 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp.h @@ -0,0 +1,70 @@ +#pragma once + +#include + +typedef struct AvrIsp AvrIsp; +typedef void (*AvrIspCallback)(void* context); + +struct AvrIspSignature { + uint8_t vendor; + uint8_t part_family; + uint8_t part_number; +}; + +typedef struct AvrIspSignature AvrIspSignature; + +AvrIsp* avr_isp_alloc(void); + +void avr_isp_free(AvrIsp* instance); + +void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context); + +bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance); + +AvrIspSignature avr_isp_read_signature(AvrIsp* instance); + +void avr_isp_end_pmode(AvrIsp* instance); + +bool avr_isp_erase_chip(AvrIsp* instance); + +uint8_t avr_isp_spi_transaction( + AvrIsp* instance, + uint8_t cmd, + uint8_t addr_hi, + uint8_t addr_lo, + uint8_t data); + +bool avr_isp_read_page( + AvrIsp* instance, + uint32_t memtype, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size); + +bool avr_isp_write_page( + AvrIsp* instance, + uint32_t mem_type, + uint32_t mem_size, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size); + +uint8_t avr_isp_read_lock_byte(AvrIsp* instance); + +bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock); + +uint8_t avr_isp_read_fuse_low(AvrIsp* instance); + +bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse); + +uint8_t avr_isp_read_fuse_high(AvrIsp* instance); + +bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse); + +uint8_t avr_isp_read_fuse_extended(AvrIsp* instance); + +bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse); + +void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_event.h b/applications/external/avr_isp_programmer/helpers/avr_isp_event.h new file mode 100644 index 0000000000..c704b5b356 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_event.h @@ -0,0 +1,23 @@ +#pragma once + +typedef enum { + //SubmenuIndex + SubmenuIndexAvrIspProgrammer = 10, + SubmenuIndexAvrIspReader, + SubmenuIndexAvrIspWriter, + SubmenuIndexAvrIsWiring, + SubmenuIndexAvrIspAbout, + + //AvrIspCustomEvent + AvrIspCustomEventSceneChipDetectOk = 100, + AvrIspCustomEventSceneReadingOk, + AvrIspCustomEventSceneWritingOk, + AvrIspCustomEventSceneErrorVerification, + AvrIspCustomEventSceneErrorReading, + AvrIspCustomEventSceneErrorWriting, + AvrIspCustomEventSceneErrorWritingFuse, + AvrIspCustomEventSceneInputName, + AvrIspCustomEventSceneSuccess, + AvrIspCustomEventSceneExit, + AvrIspCustomEventSceneExitStartMenu, +} AvrIspCustomEvent; diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_types.h b/applications/external/avr_isp_programmer/helpers/avr_isp_types.h new file mode 100644 index 0000000000..5e174ec3b9 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_types.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#define AVR_ISP_VERSION_APP "0.1" +#define AVR_ISP_DEVELOPED "SkorP" +#define AVR_ISP_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" + +#define AVR_ISP_APP_FILE_VERSION 1 +#define AVR_ISP_APP_FILE_TYPE "Flipper Dump AVR" +#define AVR_ISP_APP_EXTENSION ".avr" + +typedef enum { + //AvrIspViewVariableItemList, + AvrIspViewSubmenu, + AvrIspViewProgrammer, + AvrIspViewReader, + AvrIspViewWriter, + AvrIspViewWidget, + AvrIspViewPopup, + AvrIspViewTextInput, + AvrIspViewChipDetect, +} AvrIspView; + +typedef enum { + AvrIspErrorNoError, + AvrIspErrorReading, + AvrIspErrorWriting, + AvrIspErrorVerification, + AvrIspErrorWritingFuse, +} AvrIspError; \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c new file mode 100644 index 0000000000..dfe1f43c2c --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c @@ -0,0 +1,266 @@ +#include "avr_isp_worker.h" +#include +#include "../lib/driver/avr_isp_prog.h" +#include "../lib/driver/avr_isp_prog_cmd.h" +#include "../lib/driver/avr_isp_chip_arr.h" + +#include + +#define TAG "AvrIspWorker" + +typedef enum { + AvrIspWorkerEvtStop = (1 << 0), + + AvrIspWorkerEvtRx = (1 << 1), + AvrIspWorkerEvtTxCoplete = (1 << 2), + AvrIspWorkerEvtTx = (1 << 3), + AvrIspWorkerEvtState = (1 << 4), + + //AvrIspWorkerEvtCfg = (1 << 5), + +} AvrIspWorkerEvt; + +struct AvrIspWorker { + FuriThread* thread; + volatile bool worker_running; + uint8_t connect_usb; + AvrIspWorkerCallback callback; + void* context; +}; + +#define AVR_ISP_WORKER_PROG_ALL_EVENTS (AvrIspWorkerEvtStop) +#define AVR_ISP_WORKER_ALL_EVENTS \ + (AvrIspWorkerEvtTx | AvrIspWorkerEvtTxCoplete | AvrIspWorkerEvtRx | AvrIspWorkerEvtStop | \ + AvrIspWorkerEvtState) + +//########################/* VCP CDC */############################################# +#include "usb_cdc.h" +#include +#include +#include + +#define AVR_ISP_VCP_CDC_CH 1 +#define AVR_ISP_VCP_CDC_PKT_LEN CDC_DATA_SZ +#define AVR_ISP_VCP_UART_RX_BUF_SIZE (AVR_ISP_VCP_CDC_PKT_LEN * 5) + +static void vcp_on_cdc_tx_complete(void* context); +static void vcp_on_cdc_rx(void* context); +static void vcp_state_callback(void* context, uint8_t state); +static void vcp_on_cdc_control_line(void* context, uint8_t state); +static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config); + +static const CdcCallbacks cdc_cb = { + vcp_on_cdc_tx_complete, + vcp_on_cdc_rx, + vcp_state_callback, + vcp_on_cdc_control_line, + vcp_on_line_config, +}; + +/* VCP callbacks */ + +static void vcp_on_cdc_tx_complete(void* context) { + furi_assert(context); + AvrIspWorker* instance = context; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTxCoplete); +} + +static void vcp_on_cdc_rx(void* context) { + furi_assert(context); + AvrIspWorker* instance = context; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx); +} + +static void vcp_state_callback(void* context, uint8_t state) { + UNUSED(context); + + AvrIspWorker* instance = context; + instance->connect_usb = state; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtState); +} + +static void vcp_on_cdc_control_line(void* context, uint8_t state) { + UNUSED(context); + UNUSED(state); +} + +static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config) { + UNUSED(context); + UNUSED(config); +} + +static void avr_isp_worker_vcp_cdc_init(void* context) { + furi_hal_usb_unlock(); + Cli* cli = furi_record_open(RECORD_CLI); + //close cli + cli_session_close(cli); + //disable callbacks VCP_CDC=0 + furi_hal_cdc_set_callbacks(0, NULL, NULL); + //set 2 cdc + furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true); + //open cli VCP_CDC=0 + cli_session_open(cli, &cli_vcp); + furi_record_close(RECORD_CLI); + + furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, (CdcCallbacks*)&cdc_cb, context); +} + +static void avr_isp_worker_vcp_cdc_deinit(void) { + //disable callbacks AVR_ISP_VCP_CDC_CH + furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, NULL, NULL); + + Cli* cli = furi_record_open(RECORD_CLI); + //close cli + cli_session_close(cli); + furi_hal_usb_unlock(); + //set 1 cdc + furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); + //open cli VCP_CDC=0 + cli_session_open(cli, &cli_vcp); + furi_record_close(RECORD_CLI); +} + +//################################################################################# + +static int32_t avr_isp_worker_prog_thread(void* context) { + AvrIspProg* prog = context; + FURI_LOG_D(TAG, "AvrIspProgWorker Start"); + while(1) { + if(furi_thread_flags_get() & AvrIspWorkerEvtStop) break; + avr_isp_prog_avrisp(prog); + } + FURI_LOG_D(TAG, "AvrIspProgWorker Stop"); + return 0; +} + +static void avr_isp_worker_prog_tx_data(void* context) { + furi_assert(context); + AvrIspWorker* instance = context; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTx); +} + +/** Worker thread + * + * @param context + * @return exit code + */ +static int32_t avr_isp_worker_thread(void* context) { + AvrIspWorker* instance = context; + avr_isp_worker_vcp_cdc_init(instance); + + /* start PWM on &gpio_ext_pa4 */ + furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); + + AvrIspProg* prog = avr_isp_prog_init(); + avr_isp_prog_set_tx_callback(prog, avr_isp_worker_prog_tx_data, instance); + + uint8_t buf[AVR_ISP_VCP_UART_RX_BUF_SIZE]; + size_t len = 0; + + FuriThread* prog_thread = + furi_thread_alloc_ex("AvrIspProgWorker", 1024, avr_isp_worker_prog_thread, prog); + furi_thread_start(prog_thread); + + FURI_LOG_D(TAG, "Start"); + + while(instance->worker_running) { + uint32_t events = + furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever); + + if(events & AvrIspWorkerEvtRx) { + if(avr_isp_prog_spaces_rx(prog) >= AVR_ISP_VCP_CDC_PKT_LEN) { + len = furi_hal_cdc_receive(AVR_ISP_VCP_CDC_CH, buf, AVR_ISP_VCP_CDC_PKT_LEN); + // for(uint8_t i = 0; i < len; i++) { + // FURI_LOG_I(TAG, "--> %X", buf[i]); + // } + avr_isp_prog_rx(prog, buf, len); + } else { + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx); + } + } + + if((events & AvrIspWorkerEvtTxCoplete) || (events & AvrIspWorkerEvtTx)) { + len = avr_isp_prog_tx(prog, buf, AVR_ISP_VCP_CDC_PKT_LEN); + + // for(uint8_t i = 0; i < len; i++) { + // FURI_LOG_I(TAG, "<-- %X", buf[i]); + // } + + if(len > 0) furi_hal_cdc_send(AVR_ISP_VCP_CDC_CH, buf, len); + } + + if(events & AvrIspWorkerEvtStop) { + break; + } + + if(events & AvrIspWorkerEvtState) { + if(instance->callback) + instance->callback(instance->context, (bool)instance->connect_usb); + } + } + + FURI_LOG_D(TAG, "Stop"); + + furi_thread_flags_set(furi_thread_get_id(prog_thread), AvrIspWorkerEvtStop); + avr_isp_prog_exit(prog); + furi_delay_ms(10); + furi_thread_join(prog_thread); + furi_thread_free(prog_thread); + + avr_isp_prog_free(prog); + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + avr_isp_worker_vcp_cdc_deinit(); + return 0; +} + +AvrIspWorker* avr_isp_worker_alloc(void* context) { + furi_assert(context); + UNUSED(context); + AvrIspWorker* instance = malloc(sizeof(AvrIspWorker)); + + instance->thread = furi_thread_alloc_ex("AvrIspWorker", 2048, avr_isp_worker_thread, instance); + return instance; +} + +void avr_isp_worker_free(AvrIspWorker* instance) { + furi_assert(instance); + + furi_check(!instance->worker_running); + furi_thread_free(instance->thread); + free(instance); +} + +void avr_isp_worker_set_callback( + AvrIspWorker* instance, + AvrIspWorkerCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_worker_start(AvrIspWorker* instance) { + furi_assert(instance); + furi_assert(!instance->worker_running); + + instance->worker_running = true; + + furi_thread_start(instance->thread); +} + +void avr_isp_worker_stop(AvrIspWorker* instance) { + furi_assert(instance); + furi_assert(instance->worker_running); + + instance->worker_running = false; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtStop); + + furi_thread_join(instance->thread); +} + +bool avr_isp_worker_is_running(AvrIspWorker* instance) { + furi_assert(instance); + + return instance->worker_running; +} diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h new file mode 100644 index 0000000000..cd9897dff3 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +typedef struct AvrIspWorker AvrIspWorker; + +typedef void (*AvrIspWorkerCallback)(void* context, bool connect_usb); + +/** Allocate AvrIspWorker + * + * @param context AvrIsp* context + * @return AvrIspWorker* + */ +AvrIspWorker* avr_isp_worker_alloc(void* context); + +/** Free AvrIspWorker + * + * @param instance AvrIspWorker instance + */ +void avr_isp_worker_free(AvrIspWorker* instance); + +/** Callback AvrIspWorker + * + * @param instance AvrIspWorker instance + * @param callback AvrIspWorkerOverrunCallback callback + * @param context + */ +void avr_isp_worker_set_callback( + AvrIspWorker* instance, + AvrIspWorkerCallback callback, + void* context); + +/** Start AvrIspWorker + * + * @param instance AvrIspWorker instance + */ +void avr_isp_worker_start(AvrIspWorker* instance); + +/** Stop AvrIspWorker + * + * @param instance AvrIspWorker instance + */ +void avr_isp_worker_stop(AvrIspWorker* instance); + +/** Check if worker is running + * @param instance AvrIspWorker instance + * @return bool - true if running + */ +bool avr_isp_worker_is_running(AvrIspWorker* instance); diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c new file mode 100644 index 0000000000..fc8d3b09fe --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c @@ -0,0 +1,1145 @@ +#include "avr_isp_worker_rw.h" +#include +#include "avr_isp_types.h" +#include "avr_isp.h" +#include "../lib/driver/avr_isp_prog_cmd.h" +#include "../lib/driver/avr_isp_chip_arr.h" + +#include "flipper_i32hex_file.h" +#include + +#include + +#define TAG "AvrIspWorkerRW" + +#define NAME_PATERN_FLASH_FILE "flash.hex" +#define NAME_PATERN_EEPROM_FILE "eeprom.hex" + +struct AvrIspWorkerRW { + AvrIsp* avr_isp; + FuriThread* thread; + volatile bool worker_running; + + uint32_t chip_arr_ind; + bool chip_detect; + uint8_t lfuse; + uint8_t hfuse; + uint8_t efuse; + uint8_t lock; + float progress_flash; + float progress_eeprom; + const char* file_path; + const char* file_name; + AvrIspSignature signature; + AvrIspWorkerRWCallback callback; + void* context; + + AvrIspWorkerRWStatusCallback callback_status; + void* context_status; +}; + +typedef enum { + AvrIspWorkerRWEvtStop = (1 << 0), + + AvrIspWorkerRWEvtReading = (1 << 1), + AvrIspWorkerRWEvtVerification = (1 << 2), + AvrIspWorkerRWEvtWriting = (1 << 3), + AvrIspWorkerRWEvtWritingFuse = (1 << 4), + +} AvrIspWorkerRWEvt; +#define AVR_ISP_WORKER_ALL_EVENTS \ + (AvrIspWorkerRWEvtWritingFuse | AvrIspWorkerRWEvtWriting | AvrIspWorkerRWEvtVerification | \ + AvrIspWorkerRWEvtReading | AvrIspWorkerRWEvtStop) + +/** Worker thread + * + * @param context + * @return exit code + */ +static int32_t avr_isp_worker_rw_thread(void* context) { + AvrIspWorkerRW* instance = context; + + /* start PWM on &gpio_ext_pa4 */ + furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); + + FURI_LOG_D(TAG, "Start"); + + while(1) { + uint32_t events = + furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever); + + if(events & AvrIspWorkerRWEvtStop) { + break; + } + + if(events & AvrIspWorkerRWEvtWritingFuse) { + if(avr_isp_worker_rw_write_fuse(instance, instance->file_path, instance->file_name)) { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusEndWritingFuse); + } else { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusErrorWritingFuse); + } + } + + if(events & AvrIspWorkerRWEvtWriting) { + if(avr_isp_worker_rw_write_dump(instance, instance->file_path, instance->file_name)) { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusEndWriting); + } else { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusErrorWriting); + } + } + + if(events & AvrIspWorkerRWEvtVerification) { + if(avr_isp_worker_rw_verification(instance, instance->file_path, instance->file_name)) { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusEndVerification); + } else { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusErrorVerification); + } + } + + if(events & AvrIspWorkerRWEvtReading) { + if(avr_isp_worker_rw_read_dump(instance, instance->file_path, instance->file_name)) { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusEndReading); + } else { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusErrorReading); + } + } + } + FURI_LOG_D(TAG, "Stop"); + + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + + return 0; +} + +bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) { + furi_assert(instance); + + FURI_LOG_D(TAG, "Detecting AVR chip"); + + instance->chip_detect = false; + instance->chip_arr_ind = avr_isp_chip_arr_size + 1; + + /* start PWM on &gpio_ext_pa4 */ + furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); + + do { + if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { + FURI_LOG_E(TAG, "Well, I managed to enter the mod program"); + break; + } + instance->signature = avr_isp_read_signature(instance->avr_isp); + + if(instance->signature.vendor != 0x1E) { + //No detect chip + } else { + for(uint32_t ind = 0; ind < avr_isp_chip_arr_size; ind++) { + if(avr_isp_chip_arr[ind].avrarch != F_AVR8) continue; + if(avr_isp_chip_arr[ind].sigs[1] == instance->signature.part_family) { + if(avr_isp_chip_arr[ind].sigs[2] == instance->signature.part_number) { + FURI_LOG_D(TAG, "Detect AVR chip = \"%s\"", avr_isp_chip_arr[ind].name); + FURI_LOG_D( + TAG, + "Signature = 0x%02X 0x%02X 0x%02X", + instance->signature.vendor, + instance->signature.part_family, + instance->signature.part_number); + + switch(avr_isp_chip_arr[ind].nfuses) { + case 1: + instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp); + FURI_LOG_D(TAG, "Lfuse = %02X", instance->lfuse); + break; + case 2: + instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp); + instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp); + FURI_LOG_D( + TAG, "Lfuse = %02X Hfuse = %02X", instance->lfuse, instance->hfuse); + break; + case 3: + instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp); + instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp); + instance->efuse = avr_isp_read_fuse_extended(instance->avr_isp); + FURI_LOG_D( + TAG, + "Lfuse = %02X Hfuse = %02X Efuse = %02X", + instance->lfuse, + instance->hfuse, + instance->efuse); + break; + default: + break; + } + if(avr_isp_chip_arr[ind].nlocks == 1) { + instance->lock = avr_isp_read_lock_byte(instance->avr_isp); + FURI_LOG_D(TAG, "Lock = %02X", instance->lock); + } + instance->chip_detect = true; + instance->chip_arr_ind = ind; + break; + } + } + } + } + avr_isp_end_pmode(instance->avr_isp); + + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + + } while(0); + if(instance->callback) { + if(instance->chip_arr_ind > avr_isp_chip_arr_size) { + instance->callback(instance->context, "No detect", instance->chip_detect, 0); + } else if(instance->chip_arr_ind < avr_isp_chip_arr_size) { + instance->callback( + instance->context, + avr_isp_chip_arr[instance->chip_arr_ind].name, + instance->chip_detect, + avr_isp_chip_arr[instance->chip_arr_ind].flashsize); + } else { + instance->callback(instance->context, "Unknown", instance->chip_detect, 0); + } + } + + return instance->chip_detect; +} + +AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context) { + furi_assert(context); + UNUSED(context); + + AvrIspWorkerRW* instance = malloc(sizeof(AvrIspWorkerRW)); + instance->avr_isp = avr_isp_alloc(); + + instance->thread = + furi_thread_alloc_ex("AvrIspWorkerRW", 4096, avr_isp_worker_rw_thread, instance); + + instance->chip_detect = false; + instance->lfuse = 0; + instance->hfuse = 0; + instance->efuse = 0; + instance->lock = 0; + instance->progress_flash = 0.0f; + instance->progress_eeprom = 0.0f; + + return instance; +} + +void avr_isp_worker_rw_free(AvrIspWorkerRW* instance) { + furi_assert(instance); + + avr_isp_free(instance->avr_isp); + + furi_check(!instance->worker_running); + furi_thread_free(instance->thread); + + free(instance); +} + +void avr_isp_worker_rw_start(AvrIspWorkerRW* instance) { + furi_assert(instance); + furi_assert(!instance->worker_running); + + instance->worker_running = true; + + furi_thread_start(instance->thread); +} + +void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance) { + furi_assert(instance); + furi_assert(instance->worker_running); + + instance->worker_running = false; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtStop); + + furi_thread_join(instance->thread); +} + +bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance) { + furi_assert(instance); + + return instance->worker_running; +} + +void avr_isp_worker_rw_set_callback( + AvrIspWorkerRW* instance, + AvrIspWorkerRWCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_worker_rw_set_callback_status( + AvrIspWorkerRW* instance, + AvrIspWorkerRWStatusCallback callback_status, + void* context_status) { + furi_assert(instance); + + instance->callback_status = callback_status; + instance->context_status = context_status; +} + +float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance) { + furi_assert(instance); + + return instance->progress_flash; +} + +float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance) { + furi_assert(instance); + + return instance->progress_eeprom; +} + +static void avr_isp_worker_rw_get_dump_flash(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_check(instance->avr_isp); + + FURI_LOG_D(TAG, "Dump FLASH %s", file_path); + + FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_write( + file_path, avr_isp_chip_arr[instance->chip_arr_ind].flashoffset); + + uint8_t data[272] = {0}; + bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000); + uint8_t extended_addr = 0; + + for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset; + i < avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2; + i += avr_isp_chip_arr[instance->chip_arr_ind].pagesize / 2) { + if(send_extended_addr) { + if(extended_addr <= ((i >> 16) & 0xFF)) { + avr_isp_write_extended_addr(instance->avr_isp, extended_addr); + extended_addr = ((i >> 16) & 0xFF) + 1; + } + } + avr_isp_read_page( + instance->avr_isp, + STK_SET_FLASH_TYPE, + (uint16_t)i, + avr_isp_chip_arr[instance->chip_arr_ind].pagesize, + data, + sizeof(data)); + flipper_i32hex_file_bin_to_i32hex_set_data( + flipper_hex_flash, data, avr_isp_chip_arr[instance->chip_arr_ind].pagesize); + FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash)); + instance->progress_flash = + (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f); + } + flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_flash); + FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash)); + flipper_i32hex_file_close(flipper_hex_flash); + instance->progress_flash = 1.0f; +} + +static void avr_isp_worker_rw_get_dump_eeprom(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_check(instance->avr_isp); + + FURI_LOG_D(TAG, "Dump EEPROM %s", file_path); + + FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_write( + file_path, avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset); + + int32_t size_data = 32; + uint8_t data[256] = {0}; + + if(size_data > avr_isp_chip_arr[instance->chip_arr_ind].eepromsize) + size_data = avr_isp_chip_arr[instance->chip_arr_ind].eepromsize; + + for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset; + i < avr_isp_chip_arr[instance->chip_arr_ind].eepromsize; + i += size_data) { + avr_isp_read_page( + instance->avr_isp, STK_SET_EEPROM_TYPE, (uint16_t)i, size_data, data, sizeof(data)); + flipper_i32hex_file_bin_to_i32hex_set_data(flipper_hex_eeprom, data, size_data); + FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom)); + instance->progress_eeprom = + (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize); + } + flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_eeprom); + FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom)); + flipper_i32hex_file_close(flipper_hex_eeprom); + instance->progress_eeprom = 1.0f; +} + +bool avr_isp_worker_rw_read_dump( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + furi_assert(file_path); + furi_assert(file_name); + + FURI_LOG_D(TAG, "Read dump chip"); + + instance->progress_flash = 0.0f; + instance->progress_eeprom = 0.0f; + bool ret = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* flipper_format = flipper_format_file_alloc(storage); + FuriString* file_path_name = furi_string_alloc(); + + if(!avr_isp_worker_rw_detect_chip(instance)) { + FURI_LOG_E(TAG, "No detect AVR chip"); + } else { + do { + furi_string_printf( + file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION); + if(!flipper_format_file_open_always( + flipper_format, furi_string_get_cstr(file_path_name))) { + FURI_LOG_E(TAG, "flipper_format_file_open_always"); + break; + } + if(!flipper_format_write_header_cstr( + flipper_format, AVR_ISP_APP_FILE_TYPE, AVR_ISP_APP_FILE_VERSION)) { + FURI_LOG_E(TAG, "flipper_format_write_header_cstr"); + break; + } + if(!flipper_format_write_string_cstr( + flipper_format, "Chip name", avr_isp_chip_arr[instance->chip_arr_ind].name)) { + FURI_LOG_E(TAG, "Chip name"); + break; + } + if(!flipper_format_write_hex( + flipper_format, + "Signature", + (uint8_t*)&instance->signature, + sizeof(AvrIspSignature))) { + FURI_LOG_E(TAG, "Unable to add Signature"); + break; + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) { + if(!flipper_format_write_hex(flipper_format, "Lfuse", &instance->lfuse, 1)) { + FURI_LOG_E(TAG, "Unable to add Lfuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) { + if(!flipper_format_write_hex(flipper_format, "Hfuse", &instance->hfuse, 1)) { + FURI_LOG_E(TAG, "Unable to add Hfuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) { + if(!flipper_format_write_hex(flipper_format, "Efuse", &instance->efuse, 1)) { + FURI_LOG_E(TAG, "Unable to add Efuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) { + if(!flipper_format_write_hex(flipper_format, "Lock", &instance->lock, 1)) { + FURI_LOG_E(TAG, "Unable to add Lock"); + break; + } + } + furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_FLASH_FILE); + if(!flipper_format_write_string_cstr( + flipper_format, "Dump_flash", furi_string_get_cstr(file_path_name))) { + FURI_LOG_E(TAG, "Unable to add Dump_flash"); + break; + } + + if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { + furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_EEPROM_FILE); + if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { + if(!flipper_format_write_string_cstr( + flipper_format, "Dump_eeprom", furi_string_get_cstr(file_path_name))) { + FURI_LOG_E(TAG, "Unable to add Dump_eeprom"); + break; + } + } + } + ret = true; + } while(false); + } + + flipper_format_free(flipper_format); + furi_record_close(RECORD_STORAGE); + + if(ret) { + if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { + //Dump flash + furi_string_printf( + file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE); + avr_isp_worker_rw_get_dump_flash(instance, furi_string_get_cstr(file_path_name)); + //Dump eeprom + if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { + furi_string_printf( + file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE); + avr_isp_worker_rw_get_dump_eeprom(instance, furi_string_get_cstr(file_path_name)); + } + + avr_isp_end_pmode(instance->avr_isp); + } + } + + furi_string_free(file_path_name); + + return true; +} + +void avr_isp_worker_rw_read_dump_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtReading); +} + +static bool avr_isp_worker_rw_verification_flash(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + FURI_LOG_D(TAG, "Verification flash %s", file_path); + + instance->progress_flash = 0.0; + bool ret = true; + + FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path); + + uint8_t data_read_flash[272] = {0}; + uint8_t data_read_hex[272] = {0}; + + uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset; + bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000); + uint8_t extended_addr = 0; + + FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( + flipper_hex_flash, data_read_hex, sizeof(data_read_hex)); + + while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) || + (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) && + ret) { + switch(flipper_hex_ret.status) { + case FlipperI32HexFileStatusData: + + if(send_extended_addr) { + if(extended_addr <= ((addr >> 16) & 0xFF)) { + avr_isp_write_extended_addr(instance->avr_isp, extended_addr); + extended_addr = ((addr >> 16) & 0xFF) + 1; + } + } + + avr_isp_read_page( + instance->avr_isp, + STK_SET_FLASH_TYPE, + (uint16_t)addr, + flipper_hex_ret.data_size, + data_read_flash, + sizeof(data_read_flash)); + + if(memcmp(data_read_hex, data_read_flash, flipper_hex_ret.data_size) != 0) { + ret = false; + + FURI_LOG_E(TAG, "Verification flash error"); + FURI_LOG_E(TAG, "Addr: 0x%04lX", addr); + for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { + FURI_LOG_RAW_E("%02X ", data_read_hex[i]); + } + FURI_LOG_RAW_E("\r\n"); + for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { + FURI_LOG_RAW_E("%02X ", data_read_flash[i]); + } + FURI_LOG_RAW_E("\r\n"); + } + + addr += flipper_hex_ret.data_size / 2; + instance->progress_flash = + (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f); + break; + + case FlipperI32HexFileStatusUdateAddr: + addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16) / 2; + break; + + default: + furi_crash(TAG " Incorrect status."); + break; + } + + flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( + flipper_hex_flash, data_read_hex, sizeof(data_read_hex)); + } + + flipper_i32hex_file_close(flipper_hex_flash); + instance->progress_flash = 1.0f; + + return ret; +} + +static bool + avr_isp_worker_rw_verification_eeprom(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + FURI_LOG_D(TAG, "Verification eeprom %s", file_path); + + instance->progress_eeprom = 0.0; + bool ret = true; + + FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_read(file_path); + + uint8_t data_read_eeprom[272] = {0}; + uint8_t data_read_hex[272] = {0}; + + uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset; + + FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( + flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex)); + + while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) || + (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) && + ret) { + switch(flipper_hex_ret.status) { + case FlipperI32HexFileStatusData: + avr_isp_read_page( + instance->avr_isp, + STK_SET_EEPROM_TYPE, + (uint16_t)addr, + flipper_hex_ret.data_size, + data_read_eeprom, + sizeof(data_read_eeprom)); + + if(memcmp(data_read_hex, data_read_eeprom, flipper_hex_ret.data_size) != 0) { + ret = false; + FURI_LOG_E(TAG, "Verification eeprom error"); + FURI_LOG_E(TAG, "Addr: 0x%04lX", addr); + for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { + FURI_LOG_RAW_E("%02X ", data_read_hex[i]); + } + FURI_LOG_RAW_E("\r\n"); + for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { + FURI_LOG_RAW_E("%02X ", data_read_eeprom[i]); + } + FURI_LOG_RAW_E("\r\n"); + } + + addr += flipper_hex_ret.data_size; + instance->progress_eeprom = + (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize); + break; + + case FlipperI32HexFileStatusUdateAddr: + addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16); + break; + + default: + furi_crash(TAG " Incorrect status."); + break; + } + + flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( + flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex)); + } + + flipper_i32hex_file_close(flipper_hex_eeprom); + instance->progress_eeprom = 1.0f; + + return ret; +} + +bool avr_isp_worker_rw_verification( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + furi_assert(file_path); + furi_assert(file_name); + + FURI_LOG_D(TAG, "Verification chip"); + + instance->progress_flash = 0.0f; + instance->progress_eeprom = 0.0f; + FuriString* file_path_name = furi_string_alloc(); + + bool ret = false; + + if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { + do { + furi_string_printf( + file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE); + if(!avr_isp_worker_rw_verification_flash( + instance, furi_string_get_cstr(file_path_name))) + break; + + if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { + furi_string_printf( + file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE); + + if(!avr_isp_worker_rw_verification_eeprom( + instance, furi_string_get_cstr(file_path_name))) + break; + } + ret = true; + } while(false); + avr_isp_end_pmode(instance->avr_isp); + furi_string_free(file_path_name); + } + return ret; +} + +void avr_isp_worker_rw_verification_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtVerification); +} + +static void avr_isp_worker_rw_write_flash(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_check(instance->avr_isp); + + instance->progress_flash = 0.0; + + FURI_LOG_D(TAG, "Write Flash %s", file_path); + + uint8_t data[288] = {0}; + + FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path); + + uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset; + bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000); + uint8_t extended_addr = 0; + + FlipperI32HexFileRet flipper_hex_ret = + flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data)); + + while((flipper_hex_ret.status == FlipperI32HexFileStatusData) || + (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) { + switch(flipper_hex_ret.status) { + case FlipperI32HexFileStatusData: + + if(send_extended_addr) { + if(extended_addr <= ((addr >> 16) & 0xFF)) { + avr_isp_write_extended_addr(instance->avr_isp, extended_addr); + extended_addr = ((addr >> 16) & 0xFF) + 1; + } + } + + if(!avr_isp_write_page( + instance->avr_isp, + STK_SET_FLASH_TYPE, + avr_isp_chip_arr[instance->chip_arr_ind].flashsize, + (uint16_t)addr, + avr_isp_chip_arr[instance->chip_arr_ind].pagesize, + data, + flipper_hex_ret.data_size)) { + break; + } + addr += flipper_hex_ret.data_size / 2; + instance->progress_flash = + (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f); + break; + + case FlipperI32HexFileStatusUdateAddr: + addr = (data[0] << 24 | data[1] << 16) / 2; + break; + + default: + furi_crash(TAG " Incorrect status."); + break; + } + + flipper_hex_ret = + flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data)); + } + + flipper_i32hex_file_close(flipper_hex_flash); + instance->progress_flash = 1.0f; +} + +static void avr_isp_worker_rw_write_eeprom(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_check(instance->avr_isp); + + instance->progress_eeprom = 0.0; + uint8_t data[288] = {0}; + + FURI_LOG_D(TAG, "Write EEPROM %s", file_path); + + FlipperI32HexFile* flipper_hex_eeprom_read = flipper_i32hex_file_open_read(file_path); + + uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset; + FlipperI32HexFileRet flipper_hex_ret = + flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_eeprom_read, data, sizeof(data)); + + while((flipper_hex_ret.status == FlipperI32HexFileStatusData) || + (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) { + switch(flipper_hex_ret.status) { + case FlipperI32HexFileStatusData: + if(!avr_isp_write_page( + instance->avr_isp, + STK_SET_EEPROM_TYPE, + avr_isp_chip_arr[instance->chip_arr_ind].eepromsize, + (uint16_t)addr, + avr_isp_chip_arr[instance->chip_arr_ind].eeprompagesize, + data, + flipper_hex_ret.data_size)) { + break; + } + addr += flipper_hex_ret.data_size; + instance->progress_eeprom = + (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize); + break; + + case FlipperI32HexFileStatusUdateAddr: + addr = data[0] << 24 | data[1] << 16; + break; + + default: + furi_crash(TAG " Incorrect status."); + break; + } + + flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( + flipper_hex_eeprom_read, data, sizeof(data)); + } + + flipper_i32hex_file_close(flipper_hex_eeprom_read); + instance->progress_eeprom = 1.0f; +} + +bool avr_isp_worker_rw_write_dump( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + furi_assert(file_path); + furi_assert(file_name); + + FURI_LOG_D(TAG, "Write dump chip"); + + instance->progress_flash = 0.0f; + instance->progress_eeprom = 0.0f; + bool ret = false; + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* flipper_format = flipper_format_file_alloc(storage); + FuriString* file_path_name = furi_string_alloc(); + + FuriString* temp_str_1 = furi_string_alloc(); + FuriString* temp_str_2 = furi_string_alloc(); + uint32_t temp_data32; + + if(!avr_isp_worker_rw_detect_chip(instance)) { + FURI_LOG_E(TAG, "No detect AVR chip"); + } else { + //upload file with description + do { + furi_string_printf( + file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION); + if(!flipper_format_file_open_existing( + flipper_format, furi_string_get_cstr(file_path_name))) { + FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(file_path_name)); + break; + } + + if(!flipper_format_read_header(flipper_format, temp_str_1, &temp_data32)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + + if((!strcmp(furi_string_get_cstr(temp_str_1), AVR_ISP_APP_FILE_TYPE)) && + temp_data32 == AVR_ISP_APP_FILE_VERSION) { + } else { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + AvrIspSignature sig_read = {0}; + + if(!flipper_format_read_hex( + flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) { + FURI_LOG_E(TAG, "Missing Signature"); + break; + } + + if(memcmp( + (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) != + 0) { + FURI_LOG_E( + TAG, + "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)", + instance->signature.vendor, + instance->signature.part_family, + instance->signature.part_number, + sig_read.vendor, + sig_read.part_family, + sig_read.part_number); + break; + } + + if(!flipper_format_read_string(flipper_format, "Dump_flash", temp_str_1)) { + FURI_LOG_E(TAG, "Missing Dump_flash"); + break; + } + + //may not be + flipper_format_read_string(flipper_format, "Dump_eeprom", temp_str_2); + ret = true; + } while(false); + } + flipper_format_free(flipper_format); + furi_record_close(RECORD_STORAGE); + + if(ret) { + do { + //checking .hex files for errors + + furi_string_printf( + file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1)); + + FURI_LOG_D(TAG, "Check flash file"); + FlipperI32HexFile* flipper_hex_flash_read = + flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name)); + if(flipper_i32hex_file_check(flipper_hex_flash_read)) { + FURI_LOG_D(TAG, "Check flash file: OK"); + } else { + FURI_LOG_E(TAG, "Check flash file: Error"); + ret = false; + } + flipper_i32hex_file_close(flipper_hex_flash_read); + + if(furi_string_size(temp_str_2) > 4) { + furi_string_printf( + file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2)); + + FURI_LOG_D(TAG, "Check eeprom file"); + FlipperI32HexFile* flipper_hex_eeprom_read = + flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name)); + if(flipper_i32hex_file_check(flipper_hex_eeprom_read)) { + FURI_LOG_D(TAG, "Check eeprom file: OK"); + } else { + FURI_LOG_E(TAG, "Check eeprom file: Error"); + ret = false; + } + flipper_i32hex_file_close(flipper_hex_eeprom_read); + } + + if(!ret) break; + ret = false; + + //erase chip + FURI_LOG_D(TAG, "Erase chip"); + if(!avr_isp_erase_chip(instance->avr_isp)) { + FURI_LOG_E(TAG, "Erase chip: Error"); + break; + } + + if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { + FURI_LOG_E(TAG, "Well, I managed to enter the mod program"); + break; + } + + //write flash + furi_string_printf( + file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1)); + avr_isp_worker_rw_write_flash(instance, furi_string_get_cstr(file_path_name)); + + //write eeprom + if(furi_string_size(temp_str_2) > 4) { + furi_string_printf( + file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2)); + avr_isp_worker_rw_write_eeprom(instance, furi_string_get_cstr(file_path_name)); + } + ret = true; + avr_isp_end_pmode(instance->avr_isp); + } while(false); + } + + furi_string_free(file_path_name); + furi_string_free(temp_str_1); + furi_string_free(temp_str_2); + + return ret; +} + +void avr_isp_worker_rw_write_dump_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWriting); +} + +bool avr_isp_worker_rw_write_fuse( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + furi_assert(file_path); + furi_assert(file_name); + + FURI_LOG_D(TAG, "Write fuse chip"); + + bool ret = false; + uint8_t lfuse; + uint8_t hfuse; + uint8_t efuse; + uint8_t lock; + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* flipper_format = flipper_format_file_alloc(storage); + FuriString* temp_str = furi_string_alloc(); + + uint32_t temp_data32; + + if(!avr_isp_worker_rw_detect_chip(instance)) { + FURI_LOG_E(TAG, "No detect AVR chip"); + } else { + //upload file with description + do { + furi_string_printf(temp_str, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION); + if(!flipper_format_file_open_existing(flipper_format, furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(temp_str)); + break; + } + + if(!flipper_format_read_header(flipper_format, temp_str, &temp_data32)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + + if((!strcmp(furi_string_get_cstr(temp_str), AVR_ISP_APP_FILE_TYPE)) && + temp_data32 == AVR_ISP_APP_FILE_VERSION) { + } else { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + AvrIspSignature sig_read = {0}; + + if(!flipper_format_read_hex( + flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) { + FURI_LOG_E(TAG, "Missing Signature"); + break; + } + + if(memcmp( + (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) != + 0) { + FURI_LOG_E( + TAG, + "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)", + instance->signature.vendor, + instance->signature.part_family, + instance->signature.part_number, + sig_read.vendor, + sig_read.part_family, + sig_read.part_number); + break; + } + + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) { + if(!flipper_format_read_hex(flipper_format, "Lfuse", &lfuse, 1)) { + FURI_LOG_E(TAG, "Missing Lfuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) { + if(!flipper_format_read_hex(flipper_format, "Hfuse", &hfuse, 1)) { + FURI_LOG_E(TAG, "Missing Hfuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) { + if(!flipper_format_read_hex(flipper_format, "Efuse", &efuse, 1)) { + FURI_LOG_E(TAG, "Missing Efuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) { + if(!flipper_format_read_hex(flipper_format, "Lock", &lock, 1)) { + FURI_LOG_E(TAG, "Missing Lock"); + break; + } + } + + if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { + FURI_LOG_E(TAG, "Well, I managed to enter the mod program"); + break; + } + + ret = true; + + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) { + if(instance->lfuse != lfuse) { + if(!avr_isp_write_fuse_low(instance->avr_isp, lfuse)) { + FURI_LOG_E(TAG, "Write Lfuse: error"); + ret = false; + } + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) { + if(instance->hfuse != hfuse) { + if(!avr_isp_write_fuse_high(instance->avr_isp, hfuse)) { + FURI_LOG_E(TAG, "Write Hfuse: error"); + ret = false; + } + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) { + if(instance->efuse != efuse) { + if(!avr_isp_write_fuse_extended(instance->avr_isp, efuse)) { + FURI_LOG_E(TAG, "Write Efuse: error"); + ret = false; + } + } + } + + if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) { + FURI_LOG_D(TAG, "Write lock byte"); + if(instance->lock != lock) { + if(!avr_isp_write_lock_byte(instance->avr_isp, lock)) { + FURI_LOG_E(TAG, "Write Lock byte: error"); + ret = false; + } + } + } + avr_isp_end_pmode(instance->avr_isp); + } while(false); + } + + flipper_format_free(flipper_format); + furi_record_close(RECORD_STORAGE); + furi_string_free(temp_str); + return ret; +} + +void avr_isp_worker_rw_write_fuse_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWritingFuse); +} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h new file mode 100644 index 0000000000..2c52a8700d --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h @@ -0,0 +1,99 @@ +#pragma once + +#include + +typedef struct AvrIspWorkerRW AvrIspWorkerRW; + +typedef void (*AvrIspWorkerRWCallback)( + void* context, + const char* name, + bool detect_chip, + uint32_t flash_size); + +typedef enum { + AvrIspWorkerRWStatusILDE = 0, + AvrIspWorkerRWStatusEndReading = 1, + AvrIspWorkerRWStatusEndVerification = 2, + AvrIspWorkerRWStatusEndWriting = 3, + AvrIspWorkerRWStatusEndWritingFuse = 4, + + AvrIspWorkerRWStatusErrorReading = (-1), + AvrIspWorkerRWStatusErrorVerification = (-2), + AvrIspWorkerRWStatusErrorWriting = (-3), + AvrIspWorkerRWStatusErrorWritingFuse = (-4), + + AvrIspWorkerRWStatusReserved = 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization. +} AvrIspWorkerRWStatus; + +typedef void (*AvrIspWorkerRWStatusCallback)(void* context, AvrIspWorkerRWStatus status); + +AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context); + +void avr_isp_worker_rw_free(AvrIspWorkerRW* instance); + +void avr_isp_worker_rw_start(AvrIspWorkerRW* instance); + +void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance); + +bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance); + +void avr_isp_worker_rw_set_callback( + AvrIspWorkerRW* instance, + AvrIspWorkerRWCallback callback, + void* context); + +void avr_isp_worker_rw_set_callback_status( + AvrIspWorkerRW* instance, + AvrIspWorkerRWStatusCallback callback_status, + void* context_status); + +bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance); + +float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance); + +float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance); + +bool avr_isp_worker_rw_read_dump( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +void avr_isp_worker_rw_read_dump_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +bool avr_isp_worker_rw_verification( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +void avr_isp_worker_rw_verification_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +bool avr_isp_worker_rw_check_hex( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +bool avr_isp_worker_rw_write_dump( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +void avr_isp_worker_rw_write_dump_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +bool avr_isp_worker_rw_write_fuse( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +void avr_isp_worker_rw_write_fuse_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c new file mode 100644 index 0000000000..a8c20a0bd9 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c @@ -0,0 +1,321 @@ +#include "flipper_i32hex_file.h" +#include +#include +#include +#include +#include + +//https://en.wikipedia.org/wiki/Intel_HEX + +#define TAG "FlipperI32HexFile" + +#define COUNT_BYTE_PAYLOAD 32 //how much payload will be used + +#define I32HEX_TYPE_DATA 0x00 +#define I32HEX_TYPE_END_OF_FILE 0x01 +#define I32HEX_TYPE_EXT_LINEAR_ADDR 0x04 +#define I32HEX_TYPE_START_LINEAR_ADDR 0x05 + +struct FlipperI32HexFile { + uint32_t addr; + uint32_t addr_last; + Storage* storage; + Stream* stream; + FuriString* str_data; + FlipperI32HexFileStatus file_open; +}; + +FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr) { + furi_assert(name); + + FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile)); + instance->addr = start_addr; + instance->addr_last = 0; + instance->storage = furi_record_open(RECORD_STORAGE); + instance->stream = file_stream_alloc(instance->storage); + + if(file_stream_open(instance->stream, name, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + instance->file_open = FlipperI32HexFileStatusOpenFileWrite; + FURI_LOG_D(TAG, "Open write file %s", name); + } else { + FURI_LOG_E(TAG, "Failed to open file %s", name); + instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile; + } + instance->str_data = furi_string_alloc(instance->storage); + + return instance; +} + +FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name) { + furi_assert(name); + + FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile)); + instance->addr = 0; + instance->addr_last = 0; + instance->storage = furi_record_open(RECORD_STORAGE); + instance->stream = file_stream_alloc(instance->storage); + + if(file_stream_open(instance->stream, name, FSAM_READ, FSOM_OPEN_EXISTING)) { + instance->file_open = FlipperI32HexFileStatusOpenFileRead; + FURI_LOG_D(TAG, "Open read file %s", name); + } else { + FURI_LOG_E(TAG, "Failed to open file %s", name); + instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile; + } + instance->str_data = furi_string_alloc(instance->storage); + + return instance; +} + +void flipper_i32hex_file_close(FlipperI32HexFile* instance) { + furi_assert(instance); + + furi_string_free(instance->str_data); + file_stream_close(instance->stream); + stream_free(instance->stream); + furi_record_close(RECORD_STORAGE); +} + +FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data( + FlipperI32HexFile* instance, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + furi_assert(data); + + FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0}; + if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) { + ret.status = FlipperI32HexFileStatusErrorFileWrite; + } + uint8_t count_byte = 0; + uint32_t ind = 0; + uint8_t crc = 0; + + furi_string_reset(instance->str_data); + + if((instance->addr_last & 0xFF0000) < (instance->addr & 0xFF0000)) { + crc = 0x02 + 0x04 + ((instance->addr >> 24) & 0xFF) + ((instance->addr >> 16) & 0xFF); + crc = 0x01 + ~crc; + //I32HEX_TYPE_EXT_LINEAR_ADDR + furi_string_cat_printf( + instance->str_data, ":02000004%04lX%02X\r\n", (instance->addr >> 16), crc); + instance->addr_last = instance->addr; + } + + while(ind < data_size) { + if((ind + COUNT_BYTE_PAYLOAD) > data_size) { + count_byte = data_size - ind; + } else { + count_byte = COUNT_BYTE_PAYLOAD; + } + //I32HEX_TYPE_DATA + furi_string_cat_printf( + instance->str_data, ":%02X%04lX00", count_byte, (instance->addr & 0xFFFF)); + crc = count_byte + ((instance->addr >> 8) & 0xFF) + (instance->addr & 0xFF); + + for(uint32_t i = 0; i < count_byte; i++) { + furi_string_cat_printf(instance->str_data, "%02X", *data); + crc += *data++; + } + crc = 0x01 + ~crc; + furi_string_cat_printf(instance->str_data, "%02X\r\n", crc); + + ind += count_byte; + instance->addr += count_byte; + } + if(instance->file_open) stream_write_string(instance->stream, instance->str_data); + return ret; +} + +FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance) { + furi_assert(instance); + + FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0}; + if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) { + ret.status = FlipperI32HexFileStatusErrorFileWrite; + } + furi_string_reset(instance->str_data); + //I32HEX_TYPE_END_OF_FILE + furi_string_cat_printf(instance->str_data, ":00000001FF\r\n"); + if(instance->file_open) stream_write_string(instance->stream, instance->str_data); + return ret; +} + +void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr) { + furi_assert(instance); + + instance->addr = addr; +} + +const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance) { + furi_assert(instance); + + return furi_string_get_cstr(instance->str_data); +} + +static FlipperI32HexFileRet flipper_i32hex_file_parse_line( + FlipperI32HexFile* instance, + const char* str, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + furi_assert(data); + + char* str1; + uint32_t data_wrire_ind = 0; + uint32_t data_len = 0; + FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusErrorData, .data_size = 0}; + + //Search for start of data I32HEX + str1 = strstr(str, ":"); + do { + if(str1 == NULL) { + ret.status = FlipperI32HexFileStatusErrorData; + break; + } + str1++; + if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) { + ret.status = FlipperI32HexFileStatusErrorData; + break; + } + str1++; + if(++data_wrire_ind > data_size) { + ret.status = FlipperI32HexFileStatusErrorOverflow; + break; + } + data_len = 5 + data[0]; // +5 bytes per header and crc + while(data_len > data_wrire_ind) { + str1++; + if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) { + ret.status = FlipperI32HexFileStatusErrorData; + break; + } + str1++; + if(++data_wrire_ind > data_size) { + ret.status = FlipperI32HexFileStatusErrorOverflow; + break; + } + } + ret.status = FlipperI32HexFileStatusOK; + ret.data_size = data_wrire_ind; + + } while(0); + return ret; +} + +static bool flipper_i32hex_file_check_data(uint8_t* data, uint32_t data_size) { + furi_assert(data); + + uint8_t crc = 0; + uint32_t data_read_ind = 0; + if(data[0] > data_size) return false; + while(data_read_ind < data_size - 1) { + crc += data[data_read_ind++]; + } + return data[data_size - 1] == ((1 + ~crc) & 0xFF); +} + +static FlipperI32HexFileRet flipper_i32hex_file_parse( + FlipperI32HexFile* instance, + const char* str, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + furi_assert(data); + + FlipperI32HexFileRet ret = flipper_i32hex_file_parse_line(instance, str, data, data_size); + + if((ret.status == FlipperI32HexFileStatusOK) && (ret.data_size > 4)) { + switch(data[3]) { + case I32HEX_TYPE_DATA: + if(flipper_i32hex_file_check_data(data, ret.data_size)) { + ret.data_size -= 5; + memcpy(data, data + 4, ret.data_size); + ret.status = FlipperI32HexFileStatusData; + } else { + ret.status = FlipperI32HexFileStatusErrorCrc; + ret.data_size = 0; + } + break; + case I32HEX_TYPE_END_OF_FILE: + if(flipper_i32hex_file_check_data(data, ret.data_size)) { + ret.status = FlipperI32HexFileStatusEofFile; + ret.data_size = 0; + } else { + ret.status = FlipperI32HexFileStatusErrorCrc; + ret.data_size = 0; + } + break; + case I32HEX_TYPE_EXT_LINEAR_ADDR: + if(flipper_i32hex_file_check_data(data, ret.data_size)) { + data[0] = data[4]; + data[1] = data[5]; + data[3] = 0; + data[4] = 0; + ret.status = FlipperI32HexFileStatusUdateAddr; + ret.data_size = 4; + } else { + ret.status = FlipperI32HexFileStatusErrorCrc; + ret.data_size = 0; + } + break; + case I32HEX_TYPE_START_LINEAR_ADDR: + ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand; + ret.data_size = 0; + break; + default: + ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand; + ret.data_size = 0; + break; + } + } else { + ret.status = FlipperI32HexFileStatusErrorData; + ret.data_size = 0; + } + return ret; +} + +bool flipper_i32hex_file_check(FlipperI32HexFile* instance) { + furi_assert(instance); + + uint32_t data_size = 280; + uint8_t data[280] = {0}; + bool ret = true; + + if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) { + FURI_LOG_E(TAG, "File is not open"); + ret = false; + } else { + stream_rewind(instance->stream); + + while(stream_read_line(instance->stream, instance->str_data)) { + FlipperI32HexFileRet parse_ret = flipper_i32hex_file_parse( + instance, furi_string_get_cstr(instance->str_data), data, data_size); + + if(parse_ret.status < 0) { + ret = false; + } + } + stream_rewind(instance->stream); + } + return ret; +} + +FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data( + FlipperI32HexFile* instance, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + furi_assert(data); + + FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0}; + if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) { + ret.status = FlipperI32HexFileStatusErrorFileRead; + } else { + stream_read_line(instance->stream, instance->str_data); + ret = flipper_i32hex_file_parse( + instance, furi_string_get_cstr(instance->str_data), data, data_size); + } + + return ret; +} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h new file mode 100644 index 0000000000..765b94beb5 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +typedef struct FlipperI32HexFile FlipperI32HexFile; + +typedef enum { + FlipperI32HexFileStatusOK = 0, + FlipperI32HexFileStatusData = 2, + FlipperI32HexFileStatusUdateAddr = 3, + FlipperI32HexFileStatusEofFile = 4, + FlipperI32HexFileStatusOpenFileWrite = 5, + FlipperI32HexFileStatusOpenFileRead = 6, + + // Errors + FlipperI32HexFileStatusErrorCrc = (-1), + FlipperI32HexFileStatusErrorOverflow = (-2), + FlipperI32HexFileStatusErrorData = (-3), + FlipperI32HexFileStatusErrorUnsupportedCommand = (-4), + FlipperI32HexFileStatusErrorNoOpenFile = (-5), + FlipperI32HexFileStatusErrorFileWrite = (-6), + FlipperI32HexFileStatusErrorFileRead = (-7), + + FlipperI32HexFileStatusReserved = + 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization. +} FlipperI32HexFileStatus; + +typedef struct { + FlipperI32HexFileStatus status; + uint32_t data_size; +} FlipperI32HexFileRet; + +FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr); + +FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name); + +void flipper_i32hex_file_close(FlipperI32HexFile* instance); + +FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data( + FlipperI32HexFile* instance, + uint8_t* data, + uint32_t data_size); + +FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance); + +const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance); + +void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr); + +bool flipper_i32hex_file_check(FlipperI32HexFile* instance); + +FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data( + FlipperI32HexFile* instance, + uint8_t* data, + uint32_t data_size); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png b/applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..533787fe3569f70196d3f71e8373102dfd3967a0 GIT binary patch literal 3614 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 Vpqb^9w@m;7?5&;gRaoD1{{|C}E&c!i literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/avr_wiring.png b/applications/external/avr_isp_programmer/images/avr_wiring.png new file mode 100644 index 0000000000000000000000000000000000000000..957012405127ce4504ef8cd4e805b1fc31963026 GIT binary patch literal 4513 zcmbVPc|4Ts+kd8!eXGcp8be6Tm|)6*YcFLHIG=?!{D-BsomV_drl3k*dP_~dY zBs<9#MZaT-vc1zer}MsloX_u%_xU`_eP8$Wy_WBNU7zQ9;!ax`^KpxF0|3BhYGPo^ zdNNs;E+?3EpXE%n1ORSBZ!Gq-DHaRyqtnRV=Sct%G?HaU!PzYw*4mg@(>IT0-ZH1z z3Ufki^{+F9l4TX7xCG5&rE-UbZ5j?38nQ{W<-~#$5}5JAHj2F0xQ94qr0yqNeGq%C zeQPT8fzOB9jk&JfXM@`FC97GLJskC%ylEyXHYg@5Hk`~&qzLH&dC%4bVCyK z9|5{XAZFHWSvw$y4e;n7cuoVSl>iU9D|7t-Gi&osCJB(m_Dv9YDxv z#S!zz$uhxt1r}3xDlpYDXv1($~FH9WU=v8qd}{?wtP- zhS}a&|M=>YOgPd#+?Z|iV`JxG&$ zbAp*(SEqUc_rB@u80Q=Zm}JwN{s3^sKn8|uuhePf1OS7aaD{R`iM0k%#d`K54g1F$ zc(y&%BK2jO8}$YCxrxjpbdM7y5&H7cUFDJr9`N_NlB)GKUePIj{IEv*7yMd&0zdJb z*$wiw;aqHbZJdYjQX{b-&udQ737jH#qBf-(OxO-ymw~*E6|#YvC!S7 zhV@)(Y=Qa^{82phragUQjH|R5cNoPI)^*^r_%L-%^B} zY>S%7nrWI*nUR>0T5;vh^3?TzxM}xE-nRXmnb@r0tm-T~={8c&{y~QActI}i04mW% zzcjbX_OVS&!6DTP8R)L7hfU4%O7Exki+hQ9ZFoQa%y@ZVJoTtm`a8$Ijs@e->7T)C zfxLXt!dF{kDe_{Oq8y?Wu|Uzsw=Eut^z~#ACl|-+@akJY#pc%*bBFZn}``eOj@7QP$}%b`o}!Ld}AhB1!=b zr}Hq(c_)tDxyho*8vD>D=gHaW+7<{8L98-JQObv}IQl|3s#*3)*YKr_3N^QPBx|l~ z6&2>9u_|UNj+M5nx5zpi)3^OM?=q~o=H>I#SHrGN2z@*8>4d~1Rf}o_$<3!IEj`Vt z*reE|*!WAGTG>*5)}uPZ8t1KWe!W&RIX5|DN@Dl^ta-a(yYYPP{KJ-78tY}SBA+~o z+!}+x*S`77x3gcJVP;#<@+X4p=6@c!4Bx@+P=DsH8}mA`SMtiRkMeelV&0(qX&6a( z>*yagSobDfY#u%ppFS0tT-}R#Fkp1UNFd(3#cf(LWE{atJRWC@U6*Df6oR_O=eWP5^ z&UsGuF7A~^rCFuNKh%`gt#Z!dx z{7qTYa!Osw<(HRl>}YZD#SHToOS(vg1w5q-X*g(1WOUzM*17y_?l~ULBr$smeZ+C1KWB>u}1md1*KSp6pmUSpGaO zuxJDSO+@>n2+E*{DhE73n?VUdUcAkk330qJZPV z^}=2EZEc2Jl6sw>qcKYQUNO9+7oStDC#;tkQ5rGZP%7os_BE+gYGeL(cXGEkf7I!) z&mZ1#;OFqyo5FbIqGF;PqjeJeVx7c$5$UMF-Z5;zq`^;vG=qsu3c?!wSjh~fpj`wz zhZ#|Ssrpi<1x9x69B|5VGCgm81PxOtQ}aFlYI1vNHRe;+C!Xn0k=yV#cfa7=?#8vK z{KJK?gNhnyx)!lkr*8d6Pf(%YaQyL=LxIN=xPu!d8!1qDuUc>H5Y|oMsMU&zf@R3f zugSHjV3{{6d5W{uk#dDewHAC9gnR$w~hDMN*b2Rg^`_9Qk5L z2`Q>#_l@uM=kTMc9B+LplS=kGD{)upKl+SwksnmxsGyJ>$*;TO+RxwDA6u(GKh-m>1Wo6sQB%#Y>Lq zWnp!)A(lSjXByfg8lHiCzVO&{&qiJTGB&v6ZtVnjo_vP?8J#7eEgW~POlVXjUHHn7 z{8-SeL=3I{^_{U>PYa8itBF12KJvocgi^LEe_B!cTsprm-|)y&zDb9tOY7eaN8#yR z@}o6ZtFYA%USnR=lJehncWLV29^%$;KXGcyedEvYgPXp+%Mzir-&Ma3jJnot>}bDz zHEIvCw;Ui3khV;>DmQe>;))hF)3&JYrB+n`rB-ksc!xupziP1h{eWbj7S1;D!^tnk z{H@1c?Ph%oRN_3SeyviHXc1Da90)M9Bj6Vd+R;25YeAPS?P(-O3k_)2KzDQF?zo$ zbe_;Xc}{@#?WG`Ns?Tum`n+bXX1CkQ3&u*t=RB zPidpkpLFOu3)}hF9%7Gdw#e@N-HtMm!|<@pfiHvIy|;UF(^t|{UQ;jS?JU-R5qmt^ z(%5qJ)!QHy#F;gRt)+&*u|Uah4<-eyXD&gm$nSamc(QKyE`KXUEG1=+4Saib`y1+3 z1nav}jA7`+u%nR~fp|Iz&?C}3Nf1*ioon#kcg(HOc z5YR-Zjy41nq`@*kB{A@jAnJMF0F59m=%02qSmR$}I27`y3d2VW`d3g+mZu?D8l41D zhar>*%F4!PWBhY9xTp0;RB9&MgN&&&X41AE1Z-De~3kIYB0^Qq> z;Z5^}{IZDmq+MWWL0Q56l?Bz$(()g}z5#!8#bON}g!h9ZV9IbR^;c?tY6mcEN&g$h zziJ2Ig8fKvTT%e+0-eCx60-DfFpIwb?&y~yD;f=Jx;JZI@aGL^gbP%XFT>P83(8u7 z5xt2TWMzaZ0i{k*NLFp6%p{m4^p-vG{$|NF+{M*jI;nmS92579hp1zTh z5dvXops%WKWQal_5rzmOLxiEqZ>*_r00Zw!ApQ33&GP*>7X4qb8dy3B&!Ew9G}`&! zg>c%7#-Igw(flAt6&L~{Z;2;(`~H%g__a%aC2c^WdtW3Gjp#Hg2&7t zJSoM=wtVIDMf(|PdD-9vm4z0veG48^wvQgZ<#KZ2hUX6%U~yIe1Yo|o0s>y2!A+N% zR+mv9UAC_IKmhi54&6{HC|*sRi3ooso4&sM746&1E12$k&tINBEE7`~wCJ_KRy6<{v;mcB*uDE;s+b=}Ts+~hg(5Fvc696dtCTy>i z*KZydY0uYovJbf0j>=K7Dhkheb9eOaUP+Umpy&Z{GaEsEvKI+Z1T*6I1;^=|#QCv) z97Q_4dAYa0Hpv5zLHq0D{v*2I1^m3tHGW#~flX3+`+jzc!28a1B z{ao5u+6J~TOfE38HmStuzM;`6y5^h;CvwTl8ZTkyb5YA!R?`58zlcgCiULe1GlG4M zPxa_~US%LXm790W3j!|p`WOM)=`e3dNdPNfZLRj`1ybDwH@HN!syQ!A#D2iyra$pQ z2)e4%j&c_O&8_J02ka4(5&M{>I|Na)i5$tOM@AywErn#nhmhqdZf|z;*uNHS=2YL@ zu@gFiO4_O=pRI;?d@RTA)65rH@&SB=j(m^3nRjS>Xfpp(NUkK%eko9B3Kby;2y4hf zYmIc}bRGg>JnpoCFhSkisVjWUSFR4LXUX;Yke^@?PPY3xef2q6#GYGzfeo8)o5n8k z^+N?aeQlCKYkTf0%s9SLvdC=e> zomHy8{62eB^ZP)%5x;y|)_DDcMcxCGB#)tcJ&L jAA^Xf0&wu=0S{nUm9IHBAnNk3cbuuAl|h-lN5uaC^CHF5 literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/chif_not_found_83x37.png b/applications/external/avr_isp_programmer/images/chif_not_found_83x37.png new file mode 100644 index 0000000000000000000000000000000000000000..b03bf3567ade9b2e4ce2d29328032ebd40c89401 GIT binary patch literal 3742 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 literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/chip_error_70x22.png b/applications/external/avr_isp_programmer/images/chip_error_70x22.png new file mode 100644 index 0000000000000000000000000000000000000000..16f81178c0ec857591db3fe40830a5955db54e73 GIT binary patch literal 3688 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 literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/chip_long_70x22.png b/applications/external/avr_isp_programmer/images/chip_long_70x22.png new file mode 100644 index 0000000000000000000000000000000000000000..3edfff82de952d6f49a012154a23104e3fd935fa GIT binary patch literal 3656 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 literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png b/applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png new file mode 100644 index 0000000000000000000000000000000000000000..a299d3630239b4486e249cc501872bed5996df3b GIT binary patch literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!QjS=r55&&8K)tZJ>! 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 B&F}yK literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/link_waiting_77x56.png b/applications/external/avr_isp_programmer/images/link_waiting_77x56.png new file mode 100644 index 0000000000000000000000000000000000000000..d7d32aed59a19e24a7e1f2216c806872610a0af5 GIT binary patch literal 3883 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 + +//https://github.com/avrdudes/avrdude/blob/master/src/avrintel.c + +const AvrIspChipArr avr_isp_chip_arr[] = { // Value of -1 typically means unknown + //{mcu_name, mcuid, family, {sig, na, ture}, flstart, flsize, pgsiz, nb, bootsz, eestart, eesize, ep, rambeg, ramsiz, nf, nl, ni}, // Source + {"ATtiny4", 0, F_AVR8L, {0x1E, 0x8F, 0x0A}, 0, 0x00200, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny5", 1, F_AVR8L, {0x1E, 0x8F, 0x09}, 0, 0x00200, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny9", 2, F_AVR8L, {0x1E, 0x90, 0x08}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny10", 3, F_AVR8L, {0x1E, 0x90, 0x03}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny20", 4, F_AVR8L, {0x1E, 0x91, 0x0F}, 0, 0x00800, 0x020, 0, 0, 0, 0, 0, 0x0040, 0x0080, 1, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny40", 5, F_AVR8L, {0x1E, 0x92, 0x0E}, 0, 0x01000, 0x040, 0, 0, 0, 0, 0, 0x0040, 0x0100, 1, 1, 18}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny102", 6, F_AVR8L, {0x1E, 0x90, 0x0C}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 16}, // atdf, avrdude, boot size (manual) + {"ATtiny104", 7, F_AVR8L, {0x1E, 0x90, 0x0B}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 16}, // atdf, avrdude, boot size (manual) + + {"ATtiny11", 8, F_AVR8, {0x1E, 0x90, 0x04}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 1, 0x0060, 0x0020, 1, 1, 5}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny12", 9, F_AVR8, {0x1E, 0x90, 0x05}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 2, 0x0060, 0x0020, 1, 1, 6}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny13", 10, F_AVR8, {0x1E, 0x90, 0x07}, 0, 0x00400, 0x020, 0, 0, 0, 0x0040, 4, 0x0060, 0x0040, 2, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny13A", 11, F_AVR8, {0x1E, 0x90, 0x07}, 0, 0x00400, 0x020, 0, 0, 0, 0x0040, 4, 0x0060, 0x0040, 2, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny15", 12, F_AVR8, {0x1E, 0x90, 0x06}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 2, 0x0060, 0x0020, 1, 1, 9}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny22", 13, F_AVR8, {0x1E, 0x91, 0x06}, 0, 0x00800, -1, 0, 0, -1, -1, -1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, boot size (manual) + {"ATtiny24", 14, F_AVR8, {0x1E, 0x91, 0x0B}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny24A", 15, F_AVR8, {0x1E, 0x91, 0x0B}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny25", 16, F_AVR8, {0x1E, 0x91, 0x08}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny26", 17, F_AVR8, {0x1E, 0x91, 0x09}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 2, 1, 12}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny28", 18, F_AVR8, {0x1E, 0x91, 0x07}, 0, 0x00800, 0x002, 0, 0, 0, 0, 0, 0x0060, 0x0020, 1, 1, 6}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny43U", 19, F_AVR8, {0x1E, 0x92, 0x0C}, 0, 0x01000, 0x040, 0, 0, 0, 0x0040, 4, 0x0060, 0x0100, 3, 1, 16}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny44", 20, F_AVR8, {0x1E, 0x92, 0x07}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny44A", 21, F_AVR8, {0x1E, 0x92, 0x07}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny45", 22, F_AVR8, {0x1E, 0x92, 0x06}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny48", 23, F_AVR8, {0x1E, 0x92, 0x09}, 0, 0x01000, 0x040, 0, 0, 0, 0x0040, 4, 0x0100, 0x0100, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny84", 24, F_AVR8, {0x1E, 0x93, 0x0C}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny84A", 25, F_AVR8, {0x1E, 0x93, 0x0C}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny85", 26, F_AVR8, {0x1E, 0x93, 0x0B}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny87", 27, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny88", 28, F_AVR8, {0x1E, 0x93, 0x11}, 0, 0x02000, 0x040, 0, 0, 0, 0x0040, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny167", 29, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny261", 30, F_AVR8, {0x1E, 0x91, 0x0C}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny261A", 31, F_AVR8, {0x1E, 0x91, 0x0C}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny441", 32, F_AVR8, {0x1E, 0x92, 0x15}, 0, 0x01000, 0x010, 0, 0, 0, 0x0100, 4, 0x0100, 0x0100, 3, 1, 30}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny461", 33, F_AVR8, {0x1E, 0x92, 0x08}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny461A", 34, F_AVR8, {0x1E, 0x92, 0x08}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny828", 35, F_AVR8, {0x1E, 0x93, 0x14}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny828R", 36, F_AVR8, {0x1E, 0x93, 0x14}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // avrdude, from ATtiny828 + {"ATtiny841", 37, F_AVR8, {0x1E, 0x93, 0x15}, 0, 0x02000, 0x010, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 30}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny861", 38, F_AVR8, {0x1E, 0x93, 0x0D}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny861A", 39, F_AVR8, {0x1E, 0x93, 0x0D}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1634", 40, F_AVR8, {0x1E, 0x94, 0x12}, 0, 0x04000, 0x020, 0, 0, 0, 0x0100, 4, 0x0100, 0x0400, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1634R", 41, F_AVR8, {0x1E, 0x94, 0x12}, 0, 0x04000, 0x020, 0, 0, 0, 0x0100, 4, 0x0100, 0x0400, 3, 1, 28}, // avrdude, from ATtiny1634 + {"ATtiny2313", 42, F_AVR8, {0x1E, 0x91, 0x0A}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny2313A", 43, F_AVR8, {0x1E, 0x91, 0x0A}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny4313", 44, F_AVR8, {0x1E, 0x92, 0x0D}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega8", 45, F_AVR8, {0x1E, 0x93, 0x07}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega8A", 46, F_AVR8, {0x1E, 0x93, 0x07}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega8HVA", 47, F_AVR8, {0x1E, 0x93, 0x10}, 0, 0x02000, 0x080, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 1, 1, 21}, // atdf, avr-gcc 12.2.0 + {"ATmega8U2", 48, F_AVR8, {0x1E, 0x93, 0x89}, 0, 0x02000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega16", 49, F_AVR8, {0x1E, 0x94, 0x03}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega16A", 50, F_AVR8, {0x1E, 0x94, 0x03}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega16HVA", 51, F_AVR8, {0x1E, 0x94, 0x0C}, 0, 0x04000, 0x080, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 1, 1, 21}, // atdf, avr-gcc 12.2.0 + {"ATmega16HVB", 52, F_AVR8, {0x1E, 0x94, 0x0D}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 2, 1, 29}, // atdf, avr-gcc 12.2.0 + {"ATmega16HVBrevB", 53, F_AVR8, {0x1E, 0x94, 0x0D}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 2, 1, 29}, // atdf, avr-gcc 12.2.0 + {"ATmega16M1", 54, F_AVR8, {0x1E, 0x94, 0x84}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0 + {"ATmega16HVA2", 55, F_AVR8, {0x1E, 0x94, 0x0E}, 0, 0x04000, 0x080, -1, -1, -1, -1, -1, 0x0100, 0x0400, 2, 1, 22}, // avr-gcc 12.2.0 + {"ATmega16U2", 56, F_AVR8, {0x1E, 0x94, 0x89}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega16U4", 57, F_AVR8, {0x1E, 0x94, 0x88}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0500, 3, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32", 58, F_AVR8, {0x1E, 0x95, 0x02}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0060, 0x0800, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32A", 59, F_AVR8, {0x1E, 0x95, 0x02}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0060, 0x0800, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32HVB", 60, F_AVR8, {0x1E, 0x95, 0x10}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 2, 1, 29}, // atdf, avr-gcc 12.2.0 + {"ATmega32HVBrevB", 61, F_AVR8, {0x1E, 0x95, 0x10}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 2, 1, 29}, // atdf, avr-gcc 12.2.0 + {"ATmega32C1", 62, F_AVR8, {0x1E, 0x95, 0x86}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0 + {"ATmega32M1", 63, F_AVR8, {0x1E, 0x95, 0x84}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32U2", 64, F_AVR8, {0x1E, 0x95, 0x8A}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0400, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32U4", 65, F_AVR8, {0x1E, 0x95, 0x87}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0a00, 3, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32U6", 66, F_AVR8, {0x1E, 0x95, 0x88}, 0, 0x08000, 0x080, 4, 0x0200, -1, -1, -1, 0x0100, 0x0a00, 3, 1, 38}, // avr-gcc 12.2.0, boot size (manual) + {"ATmega48", 67, F_AVR8, {0x1E, 0x92, 0x05}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega48A", 68, F_AVR8, {0x1E, 0x92, 0x05}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega48P", 69, F_AVR8, {0x1E, 0x92, 0x0A}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega48PA", 70, F_AVR8, {0x1E, 0x92, 0x0A}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega48PB", 71, F_AVR8, {0x1E, 0x92, 0x10}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 27}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega64", 72, F_AVR8, {0x1E, 0x96, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega64A", 73, F_AVR8, {0x1E, 0x96, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega64HVE", 74, F_AVR8, {0x1E, 0x96, 0x10}, 0, 0x10000, 0x080, 4, 0x0400, -1, -1, -1, 0x0100, 0x1000, 2, 1, 25}, // avr-gcc 12.2.0, boot size (manual) + {"ATmega64C1", 75, F_AVR8, {0x1E, 0x96, 0x86}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0 + {"ATmega64M1", 76, F_AVR8, {0x1E, 0x96, 0x84}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega64HVE2", 77, F_AVR8, {0x1E, 0x96, 0x10}, 0, 0x10000, 0x080, 4, 0x0400, 0, 0x0400, 4, 0x0100, 0x1000, 2, 1, 25}, // atdf, avr-gcc 12.2.0 + {"ATmega64RFR2", 78, F_AVR8, {0x1E, 0xA6, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0200, 0x2000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega88", 79, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega88A", 80, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega88P", 81, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega88PA", 82, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega88PB", 83, F_AVR8, {0x1E, 0x93, 0x16}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 27}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega103", 84, F_AVR8, {0x1E, 0x97, 0x01}, 0, 0x20000, 0x100, 0, 0, 0, 0x1000, 1, 0x0060, 0x0fa0, 1, 1, 24}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATmega128", 85, F_AVR8, {0x1E, 0x97, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega128A", 86, F_AVR8, {0x1E, 0x97, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega128RFA1", 87, F_AVR8, {0x1E, 0xA7, 0x01}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 72}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega128RFR2", 88, F_AVR8, {0x1E, 0xA7, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega161", 89, F_AVR8, {0x1E, 0x94, 0x01}, 0, 0x04000, 0x080, 1, 0x0400, 0, 0x0200, 1, 0x0060, 0x0400, 1, 1, 21}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATmega162", 90, F_AVR8, {0x1E, 0x94, 0x04}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega163", 91, F_AVR8, {0x1E, 0x94, 0x02}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 1, 0x0060, 0x0400, 2, 1, 18}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATmega164A", 92, F_AVR8, {0x1E, 0x94, 0x0F}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega164P", 93, F_AVR8, {0x1E, 0x94, 0x0A}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega164PA", 94, F_AVR8, {0x1E, 0x94, 0x0A}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega165", 95, F_AVR8, {0x1E, 0x94, 0x10}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATmega165A", 96, F_AVR8, {0x1E, 0x94, 0x10}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega165P", 97, F_AVR8, {0x1E, 0x94, 0x07}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega165PA", 98, F_AVR8, {0x1E, 0x94, 0x07}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega168", 99, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega168A", 100, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega168P", 101, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega168PA", 102, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega168PB", 103, F_AVR8, {0x1E, 0x94, 0x15}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 27}, // atdf, avr-gcc 7.3.0, avrdude + {"ATmega169", 104, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATmega169A", 105, F_AVR8, {0x1E, 0x94, 0x11}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega169P", 106, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega169PA", 107, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega256RFR2", 108, F_AVR8, {0x1E, 0xA8, 0x02}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x2000, 8, 0x0200, 0x8000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega323", 109, F_AVR8, {0x1E, 0x95, 0x01}, 0, 0x08000, 0x080, 4, 0x0200, -1, -1, -1, 0x0060, 0x0800, 2, 1, 21}, // avr-gcc 12.2.0, boot size (manual) + {"ATmega324A", 110, F_AVR8, {0x1E, 0x95, 0x15}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega324P", 111, F_AVR8, {0x1E, 0x95, 0x08}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega324PA", 112, F_AVR8, {0x1E, 0x95, 0x11}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega324PB", 113, F_AVR8, {0x1E, 0x95, 0x17}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 51}, // atdf, avrdude + {"ATmega325", 114, F_AVR8, {0x1E, 0x95, 0x05}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega325A", 115, F_AVR8, {0x1E, 0x95, 0x05}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega325P", 116, F_AVR8, {0x1E, 0x95, 0x0D}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega325PA", 117, F_AVR8, {0x1E, 0x95, 0x0D}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega328", 118, F_AVR8, {0x1E, 0x95, 0x14}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega328P", 119, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega328PB", 120, F_AVR8, {0x1E, 0x95, 0x16}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 45}, // atdf, avr-gcc 7.3.0, avrdude + {"ATmega329", 121, F_AVR8, {0x1E, 0x95, 0x03}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega329A", 122, F_AVR8, {0x1E, 0x95, 0x03}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega329P", 123, F_AVR8, {0x1E, 0x95, 0x0B}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega329PA", 124, F_AVR8, {0x1E, 0x95, 0x0B}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega406", 125, F_AVR8, {0x1E, 0x95, 0x07}, 0, 0x0a000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0800, 2, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega640", 126, F_AVR8, {0x1E, 0x96, 0x08}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega644", 127, F_AVR8, {0x1E, 0x96, 0x09}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega644A", 128, F_AVR8, {0x1E, 0x96, 0x09}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega644P", 129, F_AVR8, {0x1E, 0x96, 0x0A}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega644PA", 130, F_AVR8, {0x1E, 0x96, 0x0A}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega644RFR2", 131, F_AVR8, {0x1E, 0xA6, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0200, 0x2000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega645", 132, F_AVR8, {0x1E, 0x96, 0x05}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega645A", 133, F_AVR8, {0x1E, 0x96, 0x05}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega645P", 134, F_AVR8, {0x1E, 0x96, 0x0D}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega649", 135, F_AVR8, {0x1E, 0x96, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega649A", 136, F_AVR8, {0x1E, 0x96, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega649P", 137, F_AVR8, {0x1E, 0x96, 0x0B}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1280", 138, F_AVR8, {0x1E, 0x97, 0x03}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1281", 139, F_AVR8, {0x1E, 0x97, 0x04}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1284", 140, F_AVR8, {0x1E, 0x97, 0x06}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x4000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1284P", 141, F_AVR8, {0x1E, 0x97, 0x05}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x4000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1284RFR2", 142, F_AVR8, {0x1E, 0xA7, 0x03}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega2560", 143, F_AVR8, {0x1E, 0x98, 0x01}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega2561", 144, F_AVR8, {0x1E, 0x98, 0x02}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega2564RFR2", 145, F_AVR8, {0x1E, 0xA8, 0x03}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x2000, 8, 0x0200, 0x8000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3250", 146, F_AVR8, {0x1E, 0x95, 0x06}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3250A", 147, F_AVR8, {0x1E, 0x95, 0x06}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3250P", 148, F_AVR8, {0x1E, 0x95, 0x0E}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3250PA", 149, F_AVR8, {0x1E, 0x95, 0x0E}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3290", 150, F_AVR8, {0x1E, 0x95, 0x04}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3290A", 151, F_AVR8, {0x1E, 0x95, 0x04}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3290P", 152, F_AVR8, {0x1E, 0x95, 0x0C}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3290PA", 153, F_AVR8, {0x1E, 0x95, 0x0C}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6450", 154, F_AVR8, {0x1E, 0x96, 0x06}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6450A", 155, F_AVR8, {0x1E, 0x96, 0x06}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6450P", 156, F_AVR8, {0x1E, 0x96, 0x0E}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6490", 157, F_AVR8, {0x1E, 0x96, 0x04}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6490A", 158, F_AVR8, {0x1E, 0x96, 0x04}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6490P", 159, F_AVR8, {0x1E, 0x96, 0x0C}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega8515", 160, F_AVR8, {0x1E, 0x93, 0x06}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0200, 2, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega8535", 161, F_AVR8, {0x1E, 0x93, 0x08}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0200, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"AT43USB320", 162, F_AVR8, {0xff, -1, -1}, 0, 0x10000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0200, -1, -1, 0}, // avr-gcc 12.2.0 + {"AT43USB355", 163, F_AVR8, {0xff, -1, -1}, 0, 0x06000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0400, -1, -1, 0}, // avr-gcc 12.2.0 + {"AT76C711", 164, F_AVR8, {0xff, -1, -1}, 0, 0x04000, -1, -1, -1, -1, -1, -1, 0x0060, 0x07a0, -1, -1, 0}, // avr-gcc 12.2.0 + {"AT86RF401", 165, F_AVR8, {0x1E, 0x91, 0x81}, 0, 0x00800, -1, -1, -1, -1, -1, -1, 0x0060, 0x0080, 0, 1, 3}, // avr-gcc 12.2.0 + {"AT90PWM1", 166, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0 + {"AT90PWM2", 167, F_AVR8, {0x1E, 0x93, 0x81}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90PWM2B", 168, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM3", 169, F_AVR8, {0x1E, 0x93, 0x81}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM3B", 170, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90CAN32", 171, F_AVR8, {0x1E, 0x95, 0x81}, 0, 0x08000, 0x100, 4, 0x0400, 0, 0x0400, 8, 0x0100, 0x0800, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90CAN64", 172, F_AVR8, {0x1E, 0x96, 0x81}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM81", 173, F_AVR8, {0x1E, 0x93, 0x88}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0100, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"AT90USB82", 174, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90SCR100", 175, F_AVR8, {0x1E, 0x96, 0xC1}, 0, 0x10000, 0x100, 4, 0x0200, -1, -1, -1, 0x0100, 0x1000, 3, 1, 38}, // avr-gcc 12.2.0, boot size (manual) + {"AT90CAN128", 176, F_AVR8, {0x1E, 0x97, 0x81}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM161", 177, F_AVR8, {0x1E, 0x94, 0x8B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"AT90USB162", 178, F_AVR8, {0x1E, 0x94, 0x82}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM216", 179, F_AVR8, {0x1E, 0x94, 0x83}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM316", 180, F_AVR8, {0x1E, 0x94, 0x83}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90USB646", 181, F_AVR8, {0x1E, 0x96, 0x82}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90USB647", 182, F_AVR8, {0x1E, 0x96, 0x82}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90S1200", 183, F_AVR8, {0x1E, 0x90, 0x01}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 1, 0x0060, 0x0020, 1, 1, 4}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90USB1286", 184, F_AVR8, {0x1E, 0x97, 0x82}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x2000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90USB1287", 185, F_AVR8, {0x1E, 0x97, 0x82}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x2000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90S2313", 186, F_AVR8, {0x1E, 0x91, 0x01}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, 1, 1, 11}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S2323", 187, F_AVR8, {0x1E, 0x91, 0x02}, 0, 0x00800, -1, 0, 0, -1, -1, -1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, boot size (manual) + {"AT90S2333", 188, F_AVR8, {0x1E, 0x91, 0x05}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, -1, -1, 14}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S2343", 189, F_AVR8, {0x1E, 0x91, 0x03}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S4414", 190, F_AVR8, {0x1E, 0x92, 0x01}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0100, 1, 1, 13}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S4433", 191, F_AVR8, {0x1E, 0x92, 0x03}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0080, 1, 1, 14}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S4434", 192, F_AVR8, {0x1E, 0x92, 0x02}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0100, 1, 1, 17}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S8515", 193, F_AVR8, {0x1E, 0x93, 0x01}, 0, 0x02000, 0x001, 0, 0, 0, 0x0200, 1, 0x0060, 0x0200, 1, 1, 13}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90C8534", 194, F_AVR8, {0xff, -1, -1}, 0, 0x02000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0100, -1, -1, 0}, // avr-gcc 12.2.0 + {"AT90S8535", 195, F_AVR8, {0x1E, 0x93, 0x03}, 0, 0x02000, 0x001, 0, 0, 0, 0x0200, 1, 0x0060, 0x0200, 1, 1, 17}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT94K", 196, F_AVR8, {0xff, -1, -1}, 0, 0x08000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0fa0, -1, -1, 0}, // avr-gcc 12.2.0 + {"ATA5272", 197, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 37}, // atdf, avr-gcc 12.2.0 + {"ATA5505", 198, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"ATA5700M322", 199, F_AVR8, {0x1E, 0x95, 0x67}, 0x08000, 0x08000, 0x040, 0, 0, 0, 0x0880, 16, 0x0200, 0x0400, 1, 1, 51}, // atdf + {"ATA5702M322", 200, F_AVR8, {0x1E, 0x95, 0x69}, 0x08000, 0x08000, 0x040, 0, 0, 0, 0x0880, 16, 0x0200, 0x0400, 1, 1, 51}, // atdf, avr-gcc 12.2.0 + {"ATA5781", 201, F_AVR8, {0x1E, 0x95, 0x64}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA5782", 202, F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 12.2.0 + {"ATA5783", 203, F_AVR8, {0x1E, 0x95, 0x66}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA5787", 204, F_AVR8, {0x1E, 0x94, 0x6C}, 0x08000, 0x05200, 0x040, 0, 0, 0, 0x0400, 16, 0x0200, 0x0800, 1, 1, 44}, // atdf + {"ATA5790", 205, F_AVR8, {0x1E, 0x94, 0x61}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 30}, // atdf, avr-gcc 12.2.0 + {"ATA5790N", 206, F_AVR8, {0x1E, 0x94, 0x62}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 31}, // atdf, avr-gcc 12.2.0 + {"ATA5791", 207, F_AVR8, {0x1E, 0x94, 0x62}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 31}, // atdf, avr-gcc 7.3.0 + {"ATA5795", 208, F_AVR8, {0x1E, 0x93, 0x61}, 0, 0x02000, 0x040, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 23}, // atdf, avr-gcc 12.2.0 + {"ATA5831", 209, F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 12.2.0 + {"ATA5832", 210, F_AVR8, {0x1E, 0x95, 0x62}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA5833", 211, F_AVR8, {0x1E, 0x95, 0x63}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA5835", 212, F_AVR8, {0x1E, 0x94, 0x6B}, 0x08000, 0x05200, 0x040, 0, 0, 0, 0x0400, 16, 0x0200, 0x0800, 1, 1, 44}, // atdf + {"ATA6285", 213, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0140, 4, 0x0100, 0x0200, 2, 1, 27}, // atdf, avr-gcc 12.2.0 + {"ATA6286", 214, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0140, 4, 0x0100, 0x0200, 2, 1, 27}, // atdf, avr-gcc 12.2.0 + {"ATA6289", 215, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, -1, -1, -1, 0x0100, 0x0200, 2, 1, 27}, // avr-gcc 12.2.0, boot size (manual) + {"ATA6612C", 216, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0 + {"ATA6613C", 217, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0 + {"ATA6614Q", 218, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0 + {"ATA6616C", 219, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"ATA6617C", 220, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"ATA8210", 221, F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 7.3.0 + {"ATA8215", 222, F_AVR8, {0x1E, 0x95, 0x64}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA8510", 223, F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 7.3.0 + {"ATA8515", 224, F_AVR8, {0x1E, 0x95, 0x63}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA664251", 225, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"M3000", 226, F_AVR8, {0xff, -1, -1}, 0, 0x10000, -1, -1, -1, -1, -1, -1, 0x1000, 0x1000, -1, -1, 0}, // avr-gcc 12.2.0 + {"LGT8F88P", 227, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // avrdude, from ATmega88 + {"LGT8F168P", 228, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // avrdude, from ATmega168P + {"LGT8F328P", 229, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // avrdude, from ATmega328P + + {"ATxmega8E5", 230, F_XMEGA, {0x1E, 0x93, 0x41}, 0, 0x02800, 0x080, 1, 0x0800, 0, 0x0200, 32, 0x2000, 0x0400, 7, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega16A4", 231, F_XMEGA, {0x1E, 0x94, 0x41}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 94}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega16A4U", 232, F_XMEGA, {0x1E, 0x94, 0x41}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega16C4", 233, F_XMEGA, {0x1E, 0x94, 0x43}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega16D4", 234, F_XMEGA, {0x1E, 0x94, 0x42}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega16E5", 235, F_XMEGA, {0x1E, 0x94, 0x45}, 0, 0x05000, 0x080, 1, 0x1000, 0, 0x0200, 32, 0x2000, 0x0800, 7, 1, 43}, // atdf, avr-gcc 7.3.0, avrdude + {"ATxmega32C3", 236, F_XMEGA, {0x1E, 0x95, 0x49}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0 + {"ATxmega32D3", 237, F_XMEGA, {0x1E, 0x95, 0x4A}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 114}, // atdf, avr-gcc 12.2.0 + {"ATxmega32A4", 238, F_XMEGA, {0x1E, 0x95, 0x41}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 94}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega32A4U", 239, F_XMEGA, {0x1E, 0x95, 0x41}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega32C4", 240, F_XMEGA, {0x1E, 0x95, 0x44}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega32D4", 241, F_XMEGA, {0x1E, 0x95, 0x42}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega32E5", 242, F_XMEGA, {0x1E, 0x95, 0x4C}, 0, 0x09000, 0x080, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 7, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64A1", 243, F_XMEGA, {0x1E, 0x96, 0x4E}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 125}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64A1U", 244, F_XMEGA, {0x1E, 0x96, 0x4E}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64B1", 245, F_XMEGA, {0x1E, 0x96, 0x52}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 81}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64A3", 246, F_XMEGA, {0x1E, 0x96, 0x42}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64A3U", 247, F_XMEGA, {0x1E, 0x96, 0x42}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64B3", 248, F_XMEGA, {0x1E, 0x96, 0x51}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 54}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64C3", 249, F_XMEGA, {0x1E, 0x96, 0x49}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64D3", 250, F_XMEGA, {0x1E, 0x96, 0x4A}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64A4", 251, F_XMEGA, {0x1E, 0x96, 0x46}, 0, 0x11000, 0x100, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude + {"ATxmega64A4U", 252, F_XMEGA, {0x1E, 0x96, 0x46}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64D4", 253, F_XMEGA, {0x1E, 0x96, 0x47}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128A1", 254, F_XMEGA, {0x1E, 0x97, 0x4C}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 125}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128A1revD", 255, F_XMEGA, {0x1E, 0x97, 0x41}, 0, 0x22000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude + {"ATxmega128A1U", 256, F_XMEGA, {0x1E, 0x97, 0x4C}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128B1", 257, F_XMEGA, {0x1E, 0x97, 0x4D}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 81}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128A3", 258, F_XMEGA, {0x1E, 0x97, 0x42}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128A3U", 259, F_XMEGA, {0x1E, 0x97, 0x42}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128B3", 260, F_XMEGA, {0x1E, 0x97, 0x4B}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 54}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128C3", 261, F_XMEGA, {0x1E, 0x97, 0x52}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128D3", 262, F_XMEGA, {0x1E, 0x97, 0x48}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128A4", 263, F_XMEGA, {0x1E, 0x97, 0x46}, 0, 0x22000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude + {"ATxmega128A4U", 264, F_XMEGA, {0x1E, 0x97, 0x46}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128D4", 265, F_XMEGA, {0x1E, 0x97, 0x47}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega192A1", 266, F_XMEGA, {0x1E, 0x97, 0x4E}, 0, 0x32000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude + {"ATxmega192A3", 267, F_XMEGA, {0x1E, 0x97, 0x44}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega192A3U", 268, F_XMEGA, {0x1E, 0x97, 0x44}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega192C3", 269, F_XMEGA, {0x1E, 0x97, 0x51}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega192D3", 270, F_XMEGA, {0x1E, 0x97, 0x49}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256A1", 271, F_XMEGA, {0x1E, 0x98, 0x46}, 0, 0x42000, 0x200, -1, -1, 0, 0x1000, 32, -1, -1, -1, -1, 0}, // avrdude + {"ATxmega256A3", 272, F_XMEGA, {0x1E, 0x98, 0x42}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256A3B", 273, F_XMEGA, {0x1E, 0x98, 0x43}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256A3BU", 274, F_XMEGA, {0x1E, 0x98, 0x43}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256A3U", 275, F_XMEGA, {0x1E, 0x98, 0x42}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256C3", 276, F_XMEGA, {0x1E, 0x98, 0x46}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256D3", 277, F_XMEGA, {0x1E, 0x98, 0x44}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega384C3", 278, F_XMEGA, {0x1E, 0x98, 0x45}, 0, 0x62000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x8000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega384D3", 279, F_XMEGA, {0x1E, 0x98, 0x47}, 0, 0x62000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x8000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude + + {"ATtiny202", 280, F_AVR8X, {0x1E, 0x91, 0x23}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny204", 281, F_AVR8X, {0x1E, 0x91, 0x22}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny212", 282, F_AVR8X, {0x1E, 0x91, 0x21}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny214", 283, F_AVR8X, {0x1E, 0x91, 0x20}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny402", 284, F_AVR8X, {0x1E, 0x92, 0x27}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny404", 285, F_AVR8X, {0x1E, 0x92, 0x26}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny406", 286, F_AVR8X, {0x1E, 0x92, 0x25}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny412", 287, F_AVR8X, {0x1E, 0x92, 0x23}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny414", 288, F_AVR8X, {0x1E, 0x92, 0x22}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny416", 289, F_AVR8X, {0x1E, 0x92, 0x21}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny416auto", 290, F_AVR8X, {0x1E, 0x92, 0x28}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf + {"ATtiny417", 291, F_AVR8X, {0x1E, 0x92, 0x20}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny424", 292, F_AVR8X, {0x1E, 0x92, 0x2C}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude + {"ATtiny426", 293, F_AVR8X, {0x1E, 0x92, 0x2B}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude + {"ATtiny427", 294, F_AVR8X, {0x1E, 0x92, 0x2A}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude + {"ATtiny804", 295, F_AVR8X, {0x1E, 0x93, 0x25}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny806", 296, F_AVR8X, {0x1E, 0x93, 0x24}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny807", 297, F_AVR8X, {0x1E, 0x93, 0x23}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny814", 298, F_AVR8X, {0x1E, 0x93, 0x22}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny816", 299, F_AVR8X, {0x1E, 0x93, 0x21}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny817", 300, F_AVR8X, {0x1E, 0x93, 0x20}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny824", 301, F_AVR8X, {0x1E, 0x93, 0x29}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude + {"ATtiny826", 302, F_AVR8X, {0x1E, 0x93, 0x28}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude + {"ATtiny827", 303, F_AVR8X, {0x1E, 0x93, 0x27}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude + {"ATtiny1604", 304, F_AVR8X, {0x1E, 0x94, 0x25}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1606", 305, F_AVR8X, {0x1E, 0x94, 0x24}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1607", 306, F_AVR8X, {0x1E, 0x94, 0x23}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1614", 307, F_AVR8X, {0x1E, 0x94, 0x22}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1616", 308, F_AVR8X, {0x1E, 0x94, 0x21}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1617", 309, F_AVR8X, {0x1E, 0x94, 0x20}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1624", 310, F_AVR8X, {0x1E, 0x94, 0x2A}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude + {"ATtiny1626", 311, F_AVR8X, {0x1E, 0x94, 0x29}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude + {"ATtiny1627", 312, F_AVR8X, {0x1E, 0x94, 0x28}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude + {"ATtiny3214", 313, F_AVR8X, {0x1E, 0x95, 0x20}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // avr-gcc 12.2.0 + {"ATtiny3216", 314, F_AVR8X, {0x1E, 0x95, 0x21}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny3217", 315, F_AVR8X, {0x1E, 0x95, 0x22}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny3224", 316, F_AVR8X, {0x1E, 0x95, 0x28}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude + {"ATtiny3226", 317, F_AVR8X, {0x1E, 0x95, 0x27}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude + {"ATtiny3227", 318, F_AVR8X, {0x1E, 0x95, 0x26}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude + {"ATmega808", 319, F_AVR8X, {0x1E, 0x93, 0x26}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega809", 320, F_AVR8X, {0x1E, 0x93, 0x2A}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1608", 321, F_AVR8X, {0x1E, 0x94, 0x27}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1609", 322, F_AVR8X, {0x1E, 0x94, 0x26}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3208", 323, F_AVR8X, {0x1E, 0x95, 0x30}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3209", 324, F_AVR8X, {0x1E, 0x95, 0x31}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega4808", 325, F_AVR8X, {0x1E, 0x96, 0x50}, 0, 0x0c000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega4809", 326, F_AVR8X, {0x1E, 0x96, 0x51}, 0, 0x0c000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude + {"AVR8EA28", 327, F_AVR8X, {0x1E, 0x93, 0x2C}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR8EA32", 328, F_AVR8X, {0x1E, 0x93, 0x2B}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR16DD14", 329, F_AVR8X, {0x1E, 0x94, 0x34}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude + {"AVR16DD20", 330, F_AVR8X, {0x1E, 0x94, 0x33}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude + {"AVR16DD28", 331, F_AVR8X, {0x1E, 0x94, 0x32}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude + {"AVR16EA28", 332, F_AVR8X, {0x1E, 0x94, 0x37}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR16DD32", 333, F_AVR8X, {0x1E, 0x94, 0x31}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude + {"AVR16EA32", 334, F_AVR8X, {0x1E, 0x94, 0x36}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR16EA48", 335, F_AVR8X, {0x1E, 0x94, 0x35}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR32DD14", 336, F_AVR8X, {0x1E, 0x95, 0x3B}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude + {"AVR32DD20", 337, F_AVR8X, {0x1E, 0x95, 0x3A}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude + {"AVR32DA28", 338, F_AVR8X, {0x1E, 0x95, 0x34}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 41}, // atdf, avrdude + {"AVR32DB28", 339, F_AVR8X, {0x1E, 0x95, 0x37}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 42}, // atdf, avrdude + {"AVR32DD28", 340, F_AVR8X, {0x1E, 0x95, 0x39}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude + {"AVR32EA28", 341, F_AVR8X, {0x1E, 0x95, 0x3E}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR32DA32", 342, F_AVR8X, {0x1E, 0x95, 0x33}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 44}, // atdf, avrdude + {"AVR32DB32", 343, F_AVR8X, {0x1E, 0x95, 0x36}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 44}, // atdf, avrdude + {"AVR32DD32", 344, F_AVR8X, {0x1E, 0x95, 0x38}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude + {"AVR32EA32", 345, F_AVR8X, {0x1E, 0x95, 0x3D}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR32DA48", 346, F_AVR8X, {0x1E, 0x95, 0x32}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 58}, // atdf, avrdude + {"AVR32DB48", 347, F_AVR8X, {0x1E, 0x95, 0x35}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 61}, // atdf, avrdude + {"AVR32EA48", 348, F_AVR8X, {0x1E, 0x95, 0x3C}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR64DD14", 349, F_AVR8X, {0x1E, 0x96, 0x1D}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude + {"AVR64DD20", 350, F_AVR8X, {0x1E, 0x96, 0x1C}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude + {"AVR64DA28", 351, F_AVR8X, {0x1E, 0x96, 0x15}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 41}, // atdf, avrdude + {"AVR64DB28", 352, F_AVR8X, {0x1E, 0x96, 0x19}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 42}, // atdf, avrdude + {"AVR64DD28", 353, F_AVR8X, {0x1E, 0x96, 0x1B}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude + {"AVR64EA28", 354, F_AVR8X, {0x1E, 0x96, 0x20}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 37}, // atdf, avrdude + {"AVR64DA32", 355, F_AVR8X, {0x1E, 0x96, 0x14}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 44}, // atdf, avrdude + {"AVR64DB32", 356, F_AVR8X, {0x1E, 0x96, 0x18}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 44}, // atdf, avrdude + {"AVR64DD32", 357, F_AVR8X, {0x1E, 0x96, 0x1A}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude + {"AVR64EA32", 358, F_AVR8X, {0x1E, 0x96, 0x1F}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 37}, // atdf, avrdude + {"AVR64DA48", 359, F_AVR8X, {0x1E, 0x96, 0x13}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 58}, // atdf, avrdude + {"AVR64DB48", 360, F_AVR8X, {0x1E, 0x96, 0x17}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 61}, // atdf, avrdude + {"AVR64EA48", 361, F_AVR8X, {0x1E, 0x96, 0x1E}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 45}, // atdf, avrdude + {"AVR64DA64", 362, F_AVR8X, {0x1E, 0x96, 0x12}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 64}, // atdf, avrdude + {"AVR64DB64", 363, F_AVR8X, {0x1E, 0x96, 0x16}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 65}, // atdf, avrdude + {"AVR128DA28", 364, F_AVR8X, {0x1E, 0x97, 0x0A}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 41}, // atdf, avrdude + {"AVR128DB28", 365, F_AVR8X, {0x1E, 0x97, 0x0E}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 42}, // atdf, avrdude + {"AVR128DA32", 366, F_AVR8X, {0x1E, 0x97, 0x09}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 44}, // atdf, avrdude + {"AVR128DB32", 367, F_AVR8X, {0x1E, 0x97, 0x0D}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 44}, // atdf, avrdude + {"AVR128DA48", 368, F_AVR8X, {0x1E, 0x97, 0x08}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 58}, // atdf, avrdude + {"AVR128DB48", 369, F_AVR8X, {0x1E, 0x97, 0x0C}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 61}, // atdf, avrdude + {"AVR128DA64", 370, F_AVR8X, {0x1E, 0x97, 0x07}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 64}, // atdf, avrdude + {"AVR128DB64", 371, F_AVR8X, {0x1E, 0x97, 0x0B}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 65}, // atdf, avrdude +}; + +const size_t avr_isp_chip_arr_size = COUNT_OF(avr_isp_chip_arr); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h new file mode 100644 index 0000000000..66f16a7b96 --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#define F_AVR8L 1 // TPI programming, ATtiny(4|5|9|10|20|40|102|104) +#define F_AVR8 2 // ISP programming with SPI, "classic" AVRs +#define F_XMEGA 4 // PDI programming, ATxmega family +#define F_AVR8X 8 // UPDI programming, newer 8-bit MCUs + +struct AvrIspChipArr { // Value of -1 typically means unknown + const char* name; // Name of part + uint16_t mcuid; // ID of MCU in 0..2039 + uint8_t avrarch; // F_AVR8L, F_AVR8, F_XMEGA or F_AVR8X + uint8_t sigs[3]; // Signature bytes + int32_t flashoffset; // Flash offset + int32_t flashsize; // Flash size + int16_t pagesize; // Flash page size + int8_t nboots; // Number of supported boot sectors + int16_t bootsize; // Size of (smallest) boot sector + int32_t eepromoffset; // EEPROM offset + int32_t eepromsize; // EEPROM size + int32_t eeprompagesize; // EEPROM page size + int32_t sramstart; // SRAM offset + int32_t sramsize; // SRAM size + int8_t nfuses; // Number of fuse bytes + int8_t nlocks; // Number of lock bytes + uint8_t ninterrupts; // Number of vectors in interrupt vector table +}; + +typedef struct AvrIspChipArr AvrIspChipArr; + +extern const AvrIspChipArr avr_isp_chip_arr[]; +extern const size_t avr_isp_chip_arr_size; \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c new file mode 100644 index 0000000000..b457e4c27b --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c @@ -0,0 +1,633 @@ +#include "avr_isp_prog.h" +#include "avr_isp_prog_cmd.h" + +#include + +#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320 +#define TAG "AvrIspProg" + +struct AvrIspProgSignature { + uint8_t vendor; + uint8_t part_family; + uint8_t part_number; +}; + +typedef struct AvrIspProgSignature AvrIspProgSignature; + +struct AvrIspProgCfgDevice { + uint8_t devicecode; + uint8_t revision; + uint8_t progtype; + uint8_t parmode; + uint8_t polling; + uint8_t selftimed; + uint8_t lockbytes; + uint8_t fusebytes; + uint8_t flashpoll; + uint16_t eeprompoll; + uint16_t pagesize; + uint16_t eepromsize; + uint32_t flashsize; +}; + +typedef struct AvrIspProgCfgDevice AvrIspProgCfgDevice; + +struct AvrIspProg { + AvrIspSpiSw* spi; + AvrIspProgCfgDevice* cfg; + FuriStreamBuffer* stream_rx; + FuriStreamBuffer* stream_tx; + + uint16_t error; + uint16_t addr; + bool pmode; + bool exit; + bool rst_active_high; + uint8_t buff[AVR_ISP_PROG_TX_RX_BUF_SIZE]; + + AvrIspProgCallback callback; + void* context; +}; + +static void avr_isp_prog_end_pmode(AvrIspProg* instance); + +AvrIspProg* avr_isp_prog_init(void) { + AvrIspProg* instance = malloc(sizeof(AvrIspProg)); + instance->cfg = malloc(sizeof(AvrIspProgCfgDevice)); + instance->stream_rx = + furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t)); + instance->stream_tx = + furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t)); + instance->rst_active_high = false; + instance->exit = false; + return instance; +} + +void avr_isp_prog_free(AvrIspProg* instance) { + furi_assert(instance); + if(instance->spi) avr_isp_prog_end_pmode(instance); + furi_stream_buffer_free(instance->stream_tx); + furi_stream_buffer_free(instance->stream_rx); + free(instance->cfg); + free(instance); +} + +size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) { + return furi_stream_buffer_spaces_available(instance->stream_rx); +} + +bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len) { + furi_assert(instance); + furi_assert(data); + furi_assert(len != 0); + size_t ret = furi_stream_buffer_send(instance->stream_rx, data, sizeof(uint8_t) * len, 0); + return ret == sizeof(uint8_t) * len; +} + +size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len) { + furi_assert(instance); + return furi_stream_buffer_receive(instance->stream_tx, data, sizeof(int8_t) * max_len, 0); +} + +void avr_isp_prog_exit(AvrIspProg* instance) { + furi_assert(instance); + instance->exit = true; +} + +void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context) { + furi_assert(instance); + furi_assert(context); + instance->callback = callback; + instance->context = context; +} + +static void avr_isp_prog_tx_ch(AvrIspProg* instance, uint8_t data) { + furi_assert(instance); + furi_stream_buffer_send(instance->stream_tx, &data, sizeof(uint8_t), FuriWaitForever); +} + +static uint8_t avr_isp_prog_getch(AvrIspProg* instance) { + furi_assert(instance); + uint8_t data[1] = {0}; + while(furi_stream_buffer_receive(instance->stream_rx, &data, sizeof(int8_t), 30) == 0) { + if(instance->exit) break; + }; + return data[0]; +} + +static void avr_isp_prog_fill(AvrIspProg* instance, size_t len) { + furi_assert(instance); + for(size_t x = 0; x < len; x++) { + instance->buff[x] = avr_isp_prog_getch(instance); + } +} + +static void avr_isp_prog_reset_target(AvrIspProg* instance, bool reset) { + furi_assert(instance); + avr_isp_spi_sw_res_set(instance->spi, (reset == instance->rst_active_high) ? true : false); +} + +static uint8_t avr_isp_prog_spi_transaction( + AvrIspProg* instance, + uint8_t cmd, + uint8_t addr_hi, + uint8_t addr_lo, + uint8_t data) { + furi_assert(instance); + + avr_isp_spi_sw_txrx(instance->spi, cmd); + avr_isp_spi_sw_txrx(instance->spi, addr_hi); + avr_isp_spi_sw_txrx(instance->spi, addr_lo); + return avr_isp_spi_sw_txrx(instance->spi, data); +} + +static void avr_isp_prog_empty_reply(AvrIspProg* instance) { + furi_assert(instance); + if(avr_isp_prog_getch(instance) == CRC_EOP) { + avr_isp_prog_tx_ch(instance, STK_INSYNC); + avr_isp_prog_tx_ch(instance, STK_OK); + } else { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } +} + +static void avr_isp_prog_breply(AvrIspProg* instance, uint8_t data) { + furi_assert(instance); + if(avr_isp_prog_getch(instance) == CRC_EOP) { + avr_isp_prog_tx_ch(instance, STK_INSYNC); + avr_isp_prog_tx_ch(instance, data); + avr_isp_prog_tx_ch(instance, STK_OK); + } else { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } +} + +static void avr_isp_prog_get_version(AvrIspProg* instance, uint8_t data) { + furi_assert(instance); + switch(data) { + case STK_HW_VER: + avr_isp_prog_breply(instance, AVR_ISP_HWVER); + break; + case STK_SW_MAJOR: + avr_isp_prog_breply(instance, AVR_ISP_SWMAJ); + break; + case STK_SW_MINOR: + avr_isp_prog_breply(instance, AVR_ISP_SWMIN); + break; + case AVP_ISP_CONNECT_TYPE: + avr_isp_prog_breply(instance, AVP_ISP_SERIAL_CONNECT_TYPE); + break; + default: + avr_isp_prog_breply(instance, AVR_ISP_RESP_0); + } +} + +static void avr_isp_prog_set_cfg(AvrIspProg* instance) { + furi_assert(instance); + // call this after reading cfg packet into buff[] + instance->cfg->devicecode = instance->buff[0]; + instance->cfg->revision = instance->buff[1]; + instance->cfg->progtype = instance->buff[2]; + instance->cfg->parmode = instance->buff[3]; + instance->cfg->polling = instance->buff[4]; + instance->cfg->selftimed = instance->buff[5]; + instance->cfg->lockbytes = instance->buff[6]; + instance->cfg->fusebytes = instance->buff[7]; + instance->cfg->flashpoll = instance->buff[8]; + // ignore (instance->buff[9] == instance->buff[8]) //FLASH polling value. Same as flashpoll + instance->cfg->eeprompoll = instance->buff[10] << 8 | instance->buff[11]; + instance->cfg->pagesize = instance->buff[12] << 8 | instance->buff[13]; + instance->cfg->eepromsize = instance->buff[14] << 8 | instance->buff[15]; + instance->cfg->flashsize = instance->buff[16] << 24 | instance->buff[17] << 16 | + instance->buff[18] << 8 | instance->buff[19]; + + // avr devices have active low reset, at89sx are active high + instance->rst_active_high = (instance->cfg->devicecode >= 0xe0); +} +static bool + avr_isp_prog_set_pmode(AvrIspProg* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + furi_assert(instance); + uint8_t res = 0; + avr_isp_spi_sw_txrx(instance->spi, a); + avr_isp_spi_sw_txrx(instance->spi, b); + res = avr_isp_spi_sw_txrx(instance->spi, c); + avr_isp_spi_sw_txrx(instance->spi, d); + return res == 0x53; +} + +static void avr_isp_prog_end_pmode(AvrIspProg* instance) { + furi_assert(instance); + if(instance->pmode) { + avr_isp_prog_reset_target(instance, false); + // We're about to take the target out of reset + // so configure SPI pins as input + + if(instance->spi) avr_isp_spi_sw_free(instance->spi); + instance->spi = NULL; + } + + instance->pmode = false; +} + +static bool avr_isp_prog_start_pmode(AvrIspProg* instance, AvrIspSpiSwSpeed spi_speed) { + furi_assert(instance); + // Reset target before driving PIN_SCK or PIN_MOSI + + // SPI.begin() will configure SS as output, + // so SPI master mode is selected. + // We have defined RESET as pin 10, + // which for many arduino's is not the SS pin. + // So we have to configure RESET as output here, + // (reset_target() first sets the correct level) + if(instance->spi) avr_isp_spi_sw_free(instance->spi); + instance->spi = avr_isp_spi_sw_init(spi_speed); + + avr_isp_prog_reset_target(instance, true); + // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm": + + // Pulse RESET after PIN_SCK is low: + avr_isp_spi_sw_sck_set(instance->spi, false); + + // discharge PIN_SCK, value arbitrally chosen + furi_delay_ms(20); + avr_isp_prog_reset_target(instance, false); + + // Pulse must be minimum 2 target CPU speed cycles + // so 100 usec is ok for CPU speeds above 20KHz + furi_delay_ms(1); + + avr_isp_prog_reset_target(instance, true); + + // Send the enable programming command: + // datasheet: must be > 20 msec + furi_delay_ms(50); + if(avr_isp_prog_set_pmode(instance, AVR_ISP_SET_PMODE)) { + instance->pmode = true; + return true; + } + return false; +} + +static AvrIspProgSignature avr_isp_prog_check_signature(AvrIspProg* instance) { + furi_assert(instance); + AvrIspProgSignature signature; + signature.vendor = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR); + signature.part_family = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY); + signature.part_number = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER); + return signature; +} + +static bool avr_isp_prog_auto_set_spi_speed_start_pmode(AvrIspProg* instance) { + AvrIspSpiSwSpeed spi_speed[] = { + AvrIspSpiSwSpeed1Mhz, + AvrIspSpiSwSpeed400Khz, + AvrIspSpiSwSpeed250Khz, + AvrIspSpiSwSpeed125Khz, + AvrIspSpiSwSpeed60Khz, + AvrIspSpiSwSpeed40Khz, + AvrIspSpiSwSpeed20Khz, + AvrIspSpiSwSpeed10Khz, + AvrIspSpiSwSpeed5Khz, + AvrIspSpiSwSpeed1Khz, + }; + for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) { + if(avr_isp_prog_start_pmode(instance, spi_speed[i])) { + AvrIspProgSignature sig = avr_isp_prog_check_signature(instance); + AvrIspProgSignature sig_examination = avr_isp_prog_check_signature(instance); //-V656 + uint8_t y = 0; + while(y < 8) { + if(memcmp( + (uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspProgSignature)) != + 0) + break; + sig_examination = avr_isp_prog_check_signature(instance); + y++; + } + if(y == 8) { + if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) { + if(i < (COUNT_OF(spi_speed) - 1)) { + avr_isp_prog_end_pmode(instance); + i++; + return avr_isp_prog_start_pmode(instance, spi_speed[i]); + } + } + return true; + } + } + } + return false; +} + +static void avr_isp_prog_universal(AvrIspProg* instance) { + furi_assert(instance); + uint8_t data; + + avr_isp_prog_fill(instance, 4); + data = avr_isp_prog_spi_transaction( + instance, instance->buff[0], instance->buff[1], instance->buff[2], instance->buff[3]); + avr_isp_prog_breply(instance, data); +} + +static void avr_isp_prog_commit(AvrIspProg* instance, uint16_t addr, uint8_t data) { + furi_assert(instance); + avr_isp_prog_spi_transaction(instance, AVR_ISP_COMMIT(addr)); + /* polling flash */ + if(data == 0xFF) { + furi_delay_ms(5); + } else { + /* polling flash */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) { + break; + }; + } + } +} + +static uint16_t avr_isp_prog_current_page(AvrIspProg* instance) { + furi_assert(instance); + uint16_t page = 0; + switch(instance->cfg->pagesize) { + case 32: + page = instance->addr & 0xFFFFFFF0; + break; + case 64: + page = instance->addr & 0xFFFFFFE0; + break; + case 128: + page = instance->addr & 0xFFFFFFC0; + break; + case 256: + page = instance->addr & 0xFFFFFF80; + break; + + default: + page = instance->addr; + break; + } + + return page; +} + +static uint8_t avr_isp_prog_write_flash_pages(AvrIspProg* instance, size_t length) { + furi_assert(instance); + size_t x = 0; + uint16_t page = avr_isp_prog_current_page(instance); + while(x < length) { + if(page != avr_isp_prog_current_page(instance)) { + --x; + avr_isp_prog_commit(instance, page, instance->buff[x++]); + page = avr_isp_prog_current_page(instance); + } + avr_isp_prog_spi_transaction( + instance, AVR_ISP_WRITE_FLASH_LO(instance->addr, instance->buff[x++])); + + avr_isp_prog_spi_transaction( + instance, AVR_ISP_WRITE_FLASH_HI(instance->addr, instance->buff[x++])); + instance->addr++; + } + + avr_isp_prog_commit(instance, page, instance->buff[--x]); + return STK_OK; +} + +static void avr_isp_prog_write_flash(AvrIspProg* instance, size_t length) { + furi_assert(instance); + avr_isp_prog_fill(instance, length); + if(avr_isp_prog_getch(instance) == CRC_EOP) { + avr_isp_prog_tx_ch(instance, STK_INSYNC); + avr_isp_prog_tx_ch(instance, avr_isp_prog_write_flash_pages(instance, length)); + } else { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } +} + +// write (length) bytes, (start) is a byte address +static uint8_t + avr_isp_prog_write_eeprom_chunk(AvrIspProg* instance, uint16_t start, uint16_t length) { + furi_assert(instance); + // this writes byte-by-byte, + // page writing may be faster (4 bytes at a time) + avr_isp_prog_fill(instance, length); + for(uint16_t x = 0; x < length; x++) { + uint16_t addr = start + x; + avr_isp_prog_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, instance->buff[x])); + furi_delay_ms(10); + } + return STK_OK; +} + +static uint8_t avr_isp_prog_write_eeprom(AvrIspProg* instance, size_t length) { + furi_assert(instance); + // here is a word address, get the byte address + uint16_t start = instance->addr * 2; + uint16_t remaining = length; + if(length > instance->cfg->eepromsize) { + instance->error++; + return STK_FAILED; + } + while(remaining > AVR_ISP_EECHUNK) { + avr_isp_prog_write_eeprom_chunk(instance, start, AVR_ISP_EECHUNK); + start += AVR_ISP_EECHUNK; + remaining -= AVR_ISP_EECHUNK; + } + avr_isp_prog_write_eeprom_chunk(instance, start, remaining); + return STK_OK; +} + +static void avr_isp_prog_program_page(AvrIspProg* instance) { + furi_assert(instance); + uint8_t result = STK_FAILED; + uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance); + uint8_t memtype = avr_isp_prog_getch(instance); + // flash memory @addr, (length) bytes + if(memtype == STK_SET_FLASH_TYPE) { + avr_isp_prog_write_flash(instance, length); + return; + } + if(memtype == STK_SET_EEPROM_TYPE) { + result = avr_isp_prog_write_eeprom(instance, length); + if(avr_isp_prog_getch(instance) == CRC_EOP) { + avr_isp_prog_tx_ch(instance, STK_INSYNC); + avr_isp_prog_tx_ch(instance, result); + + } else { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } + return; + } + avr_isp_prog_tx_ch(instance, STK_FAILED); + return; +} + +static uint8_t avr_isp_prog_flash_read_page(AvrIspProg* instance, uint16_t length) { + furi_assert(instance); + for(uint16_t x = 0; x < length; x += 2) { + avr_isp_prog_tx_ch( + instance, + avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(instance->addr))); + avr_isp_prog_tx_ch( + instance, + avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(instance->addr))); + instance->addr++; + } + return STK_OK; +} + +static uint8_t avr_isp_prog_eeprom_read_page(AvrIspProg* instance, uint16_t length) { + furi_assert(instance); + // here again we have a word address + uint16_t start = instance->addr * 2; + for(uint16_t x = 0; x < length; x++) { + uint16_t addr = start + x; + avr_isp_prog_tx_ch( + instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr))); + } + return STK_OK; +} + +static void avr_isp_prog_read_page(AvrIspProg* instance) { + furi_assert(instance); + uint8_t result = STK_FAILED; + uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance); + uint8_t memtype = avr_isp_prog_getch(instance); + if(avr_isp_prog_getch(instance) != CRC_EOP) { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + return; + } + avr_isp_prog_tx_ch(instance, STK_INSYNC); + if(memtype == STK_SET_FLASH_TYPE) result = avr_isp_prog_flash_read_page(instance, length); + if(memtype == STK_SET_EEPROM_TYPE) result = avr_isp_prog_eeprom_read_page(instance, length); + avr_isp_prog_tx_ch(instance, result); +} + +static void avr_isp_prog_read_signature(AvrIspProg* instance) { + furi_assert(instance); + if(avr_isp_prog_getch(instance) != CRC_EOP) { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + return; + } + avr_isp_prog_tx_ch(instance, STK_INSYNC); + + avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR)); + avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY)); + avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER)); + + avr_isp_prog_tx_ch(instance, STK_OK); +} + +void avr_isp_prog_avrisp(AvrIspProg* instance) { + furi_assert(instance); + uint8_t ch = avr_isp_prog_getch(instance); + + switch(ch) { + case STK_GET_SYNC: + FURI_LOG_D(TAG, "cmd STK_GET_SYNC"); + instance->error = 0; + avr_isp_prog_empty_reply(instance); + break; + case STK_GET_SIGN_ON: + FURI_LOG_D(TAG, "cmd STK_GET_SIGN_ON"); + if(avr_isp_prog_getch(instance) == CRC_EOP) { + avr_isp_prog_tx_ch(instance, STK_INSYNC); + + avr_isp_prog_tx_ch(instance, 'A'); + avr_isp_prog_tx_ch(instance, 'V'); + avr_isp_prog_tx_ch(instance, 'R'); + avr_isp_prog_tx_ch(instance, ' '); + avr_isp_prog_tx_ch(instance, 'I'); + avr_isp_prog_tx_ch(instance, 'S'); + avr_isp_prog_tx_ch(instance, 'P'); + + avr_isp_prog_tx_ch(instance, STK_OK); + } else { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } + break; + case STK_GET_PARAMETER: + FURI_LOG_D(TAG, "cmd STK_GET_PARAMETER"); + avr_isp_prog_get_version(instance, avr_isp_prog_getch(instance)); + break; + case STK_SET_DEVICE: + FURI_LOG_D(TAG, "cmd STK_SET_DEVICE"); + avr_isp_prog_fill(instance, 20); + avr_isp_prog_set_cfg(instance); + avr_isp_prog_empty_reply(instance); + break; + case STK_SET_DEVICE_EXT: // ignore for now + FURI_LOG_D(TAG, "cmd STK_SET_DEVICE_EXT"); + avr_isp_prog_fill(instance, 5); + avr_isp_prog_empty_reply(instance); + break; + case STK_ENTER_PROGMODE: + FURI_LOG_D(TAG, "cmd STK_ENTER_PROGMODE"); + if(!instance->pmode) avr_isp_prog_auto_set_spi_speed_start_pmode(instance); + avr_isp_prog_empty_reply(instance); + break; + case STK_LOAD_ADDRESS: + FURI_LOG_D(TAG, "cmd STK_LOAD_ADDRESS"); + instance->addr = avr_isp_prog_getch(instance) | avr_isp_prog_getch(instance) << 8; + avr_isp_prog_empty_reply(instance); + break; + case STK_PROG_FLASH: // ignore for now + FURI_LOG_D(TAG, "cmd STK_PROG_FLASH"); + avr_isp_prog_getch(instance); + avr_isp_prog_getch(instance); + avr_isp_prog_empty_reply(instance); + break; + case STK_PROG_DATA: // ignore for now + FURI_LOG_D(TAG, "cmd STK_PROG_DATA"); + avr_isp_prog_getch(instance); + avr_isp_prog_empty_reply(instance); + break; + case STK_PROG_PAGE: + FURI_LOG_D(TAG, "cmd STK_PROG_PAGE"); + avr_isp_prog_program_page(instance); + break; + case STK_READ_PAGE: + FURI_LOG_D(TAG, "cmd STK_READ_PAGE"); + avr_isp_prog_read_page(instance); + break; + case STK_UNIVERSAL: + FURI_LOG_D(TAG, "cmd STK_UNIVERSAL"); + avr_isp_prog_universal(instance); + break; + case STK_LEAVE_PROGMODE: + FURI_LOG_D(TAG, "cmd STK_LEAVE_PROGMODE"); + instance->error = 0; + if(instance->pmode) avr_isp_prog_end_pmode(instance); + avr_isp_prog_empty_reply(instance); + break; + case STK_READ_SIGN: + FURI_LOG_D(TAG, "cmd STK_READ_SIGN"); + avr_isp_prog_read_signature(instance); + break; + // expecting a command, not CRC_EOP + // this is how we can get back in sync + case CRC_EOP: + FURI_LOG_D(TAG, "cmd CRC_EOP"); + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + break; + // anything else we will return STK_UNKNOWN + default: + FURI_LOG_D(TAG, "cmd STK_ERROR_CMD"); + instance->error++; + if(avr_isp_prog_getch(instance) == CRC_EOP) + avr_isp_prog_tx_ch(instance, STK_UNKNOWN); + else + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } + + if(instance->callback) { + instance->callback(instance->context); + } +} diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h new file mode 100644 index 0000000000..2c15ab0663 --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h @@ -0,0 +1,16 @@ +#pragma once + +#include "avr_isp_spi_sw.h" +#include + +typedef struct AvrIspProg AvrIspProg; +typedef void (*AvrIspProgCallback)(void* context); + +AvrIspProg* avr_isp_prog_init(void); +void avr_isp_prog_free(AvrIspProg* instance); +size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) ; +bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len); +size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len); +void avr_isp_prog_avrisp(AvrIspProg* instance); +void avr_isp_prog_exit(AvrIspProg* instance); +void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context); diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h new file mode 100644 index 0000000000..f8b07203e6 --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h @@ -0,0 +1,97 @@ +#pragma once + +// http://ww1.microchip.com/downloads/en/appnotes/atmel-0943-in-system-programming_applicationnote_avr910.pdf +// AVR ISP Definitions +#define AVR_ISP_HWVER 0X02 +#define AVR_ISP_SWMAJ 0X01 +#define AVR_ISP_SWMIN 0X12 +#define AVP_ISP_SERIAL_CONNECT_TYPE 0X53 +#define AVP_ISP_CONNECT_TYPE 0x93 +#define AVR_ISP_RESP_0 0X00 + +#define AVR_ISP_SET_PMODE 0xAC, 0x53, 0x00, 0x00 +#define AVR_ISP_READ_VENDOR 0x30, 0x00, 0x00, 0x00 +#define AVR_ISP_READ_PART_FAMILY 0x30, 0x00, 0x01, 0x00 +#define AVR_ISP_READ_PART_NUMBER 0x30, 0x00, 0x02, 0x00 +#define AVR_ISP_ERASE_CHIP \ + 0xAC, 0x80, 0x00, 0x00 //Erase Chip, Wait N ms, Release RESET to end the erase. +//The only way to end a Chip Erase cycle is by temporarily releasing the Reset line + +#define AVR_ISP_EXTENDED_ADDR(data) 0x4D, 0x00, data, 0x00 +#define AVR_ISP_WRITE_FLASH_LO(add, data) 0x40, (add >> 8) & 0xFF, add & 0xFF, data +#define AVR_ISP_WRITE_FLASH_HI(add, data) 0x48, (add >> 8) & 0xFF, add & 0xFF, data +#define AVR_ISP_READ_FLASH_LO(add) 0x20, (add >> 8) & 0xFF, add & 0xFF, 0x00 +#define AVR_ISP_READ_FLASH_HI(add) 0x28, (add >> 8) & 0xFF, add & 0xFF, 0x00 + +#define AVR_ISP_WRITE_EEPROM(add, data) \ + 0xC0, (add >> 8) & 0xFF, add & 0xFF, data //Send cmd, Wait N ms +#define AVR_ISP_READ_EEPROM(add) 0xA0, (add >> 8) & 0xFF, add & 0xFF, 0xFF + +#define AVR_ISP_COMMIT(add) \ + 0x4C, (add >> 8) & 0xFF, add & 0xFF, 0x00 //Send cmd, polling read last addr page + +#define AVR_ISP_OSCCAL(add) 0x38, 0x00, add, 0x00 + +#define AVR_ISP_WRITE_LOCK_BYTE(data) 0xAC, 0xE0, 0x00, data //Send cmd, Wait N ms +#define AVR_ISP_READ_LOCK_BYTE 0x58, 0x00, 0x00, 0x00 +#define AVR_ISP_WRITE_FUSE_LOW(data) 0xAC, 0xA0, 0x00, data //Send cmd, Wait N ms +#define AVR_ISP_READ_FUSE_LOW 0x50, 0x00, 0x00, 0x00 +#define AVR_ISP_WRITE_FUSE_HIGH(data) 0xAC, 0xA8, 0x00, data //Send cmd, Wait N ms +#define AVR_ISP_READ_FUSE_HIGH 0x58, 0x08, 0x00, 0x00 +#define AVR_ISP_WRITE_FUSE_EXTENDED(data) 0xAC, 0xA4, 0x00, data //Send cmd, Wait N ms (~write) +#define AVR_ISP_READ_FUSE_EXTENDED 0x50, 0x08, 0x00, 0x00 + +#define AVR_ISP_EECHUNK 0x20 + +// https://www.microchip.com/content/dam/mchp/documents/OTH/ApplicationNotes/ApplicationNotes/doc2525.pdf +// STK Definitions +#define STK_OK 0x10 +#define STK_FAILED 0x11 +#define STK_UNKNOWN 0x12 +#define STK_INSYNC 0x14 +#define STK_NOSYNC 0x15 +#define CRC_EOP 0x20 + +#define STK_GET_SYNC 0x30 +#define STK_GET_SIGN_ON 0x31 +#define STK_SET_PARAMETER 0x40 +#define STK_GET_PARAMETER 0x41 +#define STK_SET_DEVICE 0x42 +#define STK_SET_DEVICE_EXT 0x45 +#define STK_ENTER_PROGMODE 0x50 +#define STK_LEAVE_PROGMODE 0x51 +#define STK_CHIP_ERASE 0x52 +#define STK_CHECK_AUTOINC 0x53 +#define STK_LOAD_ADDRESS 0x55 +#define STK_UNIVERSAL 0x56 +#define STK_UNIVERSAL_MULTI 0x57 +#define STK_PROG_FLASH 0x60 +#define STK_PROG_DATA 0x61 +#define STK_PROG_FUSE 0x62 +#define STK_PROG_FUSE_EXT 0x65 +#define STK_PROG_LOCK 0x63 +#define STK_PROG_PAGE 0x64 +#define STK_READ_FLASH 0x70 +#define STK_READ_DATA 0x71 +#define STK_READ_FUSE 0x72 +#define STK_READ_LOCK 0x73 +#define STK_READ_PAGE 0x74 +#define STK_READ_SIGN 0x75 +#define STK_READ_OSCCAL 0x76 +#define STK_READ_FUSE_EXT 0x77 +#define STK_READ_OSCCAL_EXT 0x78 +#define STK_HW_VER 0x80 +#define STK_SW_MAJOR 0x81 +#define STK_SW_MINOR 0x82 +#define STK_LEDS 0x83 +#define STK_VTARGET 0x84 +#define STK_VADJUST 0x85 +#define STK_OSC_PSCALE 0x86 +#define STK_OSC_CMATCH 0x87 +#define STK_SCK_DURATION 0x89 +#define STK_BUFSIZEL 0x90 +#define STK_BUFSIZEH 0x91 +#define STK_STK500_TOPCARD_DETECT 0x98 + +#define STK_SET_EEPROM_TYPE 0X45 +#define STK_SET_FLASH_TYPE 0X46 diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c new file mode 100644 index 0000000000..f60850c841 --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c @@ -0,0 +1,73 @@ +#include "avr_isp_spi_sw.h" + +#include + +#define AVR_ISP_SPI_SW_MISO &gpio_ext_pa6 +#define AVR_ISP_SPI_SW_MOSI &gpio_ext_pa7 +#define AVR_ISP_SPI_SW_SCK &gpio_ext_pb3 +#define AVR_ISP_RESET &gpio_ext_pb2 + +struct AvrIspSpiSw { + AvrIspSpiSwSpeed speed_wait_time; + const GpioPin* miso; + const GpioPin* mosi; + const GpioPin* sck; + const GpioPin* res; +}; + +AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed) { + AvrIspSpiSw* instance = malloc(sizeof(AvrIspSpiSw)); + instance->speed_wait_time = speed; + + instance->miso = AVR_ISP_SPI_SW_MISO; + instance->mosi = AVR_ISP_SPI_SW_MOSI; + instance->sck = AVR_ISP_SPI_SW_SCK; + instance->res = AVR_ISP_RESET; + + furi_hal_gpio_init(instance->miso, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(instance->mosi, false); + furi_hal_gpio_init(instance->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(instance->sck, false); + furi_hal_gpio_init(instance->sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(instance->res, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + + return instance; +} + +void avr_isp_spi_sw_free(AvrIspSpiSw* instance) { + furi_assert(instance); + furi_hal_gpio_init(instance->res, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(instance->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(instance->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(instance->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + free(instance); +} + +uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data) { + furi_assert(instance); + for(uint8_t i = 0; i < 8; ++i) { + furi_hal_gpio_write(instance->mosi, (data & 0x80) ? true : false); + + furi_hal_gpio_write(instance->sck, true); + if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz) + furi_delay_us(instance->speed_wait_time - 1); + + data = (data << 1) | furi_hal_gpio_read(instance->miso); //-V792 + + furi_hal_gpio_write(instance->sck, false); + if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz) + furi_delay_us(instance->speed_wait_time - 1); + } + return data; +} + +void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state) { + furi_assert(instance); + furi_hal_gpio_write(instance->res, state); +} + +void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state) { + furi_assert(instance); + furi_hal_gpio_write(instance->sck, state); +} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h new file mode 100644 index 0000000000..44de5ff79c --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +typedef enum { + AvrIspSpiSwSpeed1Mhz = 0, + AvrIspSpiSwSpeed400Khz = 1, + AvrIspSpiSwSpeed250Khz = 2, + AvrIspSpiSwSpeed125Khz = 4, + AvrIspSpiSwSpeed60Khz = 8, + AvrIspSpiSwSpeed40Khz = 12, + AvrIspSpiSwSpeed20Khz = 24, + AvrIspSpiSwSpeed10Khz = 48, + AvrIspSpiSwSpeed5Khz = 96, + AvrIspSpiSwSpeed1Khz = 480, +} AvrIspSpiSwSpeed; + +typedef struct AvrIspSpiSw AvrIspSpiSw; + +AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed); +void avr_isp_spi_sw_free(AvrIspSpiSw* instance); +uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data); +void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state); +void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/clock.png b/applications/external/avr_isp_programmer/lib/driver/clock.png new file mode 100644 index 0000000000000000000000000000000000000000..93a59fe681d26795d0db342a0c55ba42fc2c7529 GIT binary patch literal 3649 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 LTALIbyM_M?u*zuP literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c new file mode 100644 index 0000000000..4af125aeea --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c @@ -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, +}; diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h new file mode 100644 index 0000000000..658ee74326 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// 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 diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c new file mode 100644 index 0000000000..e5f530fec8 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c @@ -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); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c new file mode 100644 index 0000000000..79c2393907 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c @@ -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); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h new file mode 100644 index 0000000000..6f22511dbe --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h @@ -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) diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c new file mode 100644 index 0000000000..3394f4362a --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c @@ -0,0 +1,89 @@ +#include "../avr_isp_app_i.h" +#include + +#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); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c new file mode 100644 index 0000000000..e8890e3738 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c @@ -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); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c new file mode 100644 index 0000000000..0915e1e8a2 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c @@ -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); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c new file mode 100644 index 0000000000..8dcb475970 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c @@ -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); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c new file mode 100644 index 0000000000..b00bfefce2 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c @@ -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); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c new file mode 100644 index 0000000000..a88ed28aa8 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c @@ -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); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c new file mode 100644 index 0000000000..787ed56732 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c @@ -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); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c new file mode 100644 index 0000000000..39c944fd5f --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c @@ -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); +} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c new file mode 100644 index 0000000000..fdcf71c36c --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c @@ -0,0 +1,213 @@ +#include "avr_isp_view_chip_detect.h" +#include +#include + +#include "../helpers/avr_isp_worker_rw.h" + +struct AvrIspChipDetectView { + View* view; + AvrIspWorkerRW* avr_isp_worker_rw; + AvrIspChipDetectViewCallback callback; + void* context; +}; + +typedef struct { + uint16_t idx; + const char* name_chip; + uint32_t flash_size; + AvrIspChipDetectViewState state; +} AvrIspChipDetectViewModel; + +void avr_isp_chip_detect_view_set_callback( + AvrIspChipDetectView* instance, + AvrIspChipDetectViewCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state) { + furi_assert(instance); + + with_view_model( + instance->view, AvrIspChipDetectViewModel * model, { model->state = state; }, true); +} + +void avr_isp_chip_detect_view_draw(Canvas* canvas, AvrIspChipDetectViewModel* model) { + canvas_clear(canvas); + + char str_buf[64] = {0}; + canvas_set_font(canvas, FontPrimary); + + switch(model->state) { + case AvrIspChipDetectViewStateDetected: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip detected!"); + canvas_draw_icon(canvas, 29, 14, &I_chip_long_70x22); + canvas_set_font(canvas, FontSecondary); + snprintf(str_buf, sizeof(str_buf), "%ld Kb", model->flash_size / 1024); + canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignCenter, str_buf); + canvas_draw_str_aligned(canvas, 64, 45, AlignCenter, AlignCenter, model->name_chip); + elements_button_right(canvas, "Next"); + break; + case AvrIspChipDetectViewStateErrorOccured: + canvas_draw_str_aligned( + canvas, 64, 5, AlignCenter, AlignCenter, "Error occured, try again!"); + canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 45, AlignCenter, AlignCenter, "Check the wiring and retry"); + break; + case AvrIspChipDetectViewStateErrorVerification: + canvas_draw_str_aligned( + canvas, 64, 5, AlignCenter, AlignCenter, "Data verification failed"); + canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 45, AlignCenter, AlignCenter, "Try to restart the process"); + break; + + default: + //AvrIspChipDetectViewStateNoDetect + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip not found!"); + canvas_draw_icon(canvas, 29, 12, &I_chif_not_found_83x37); + + break; + } + canvas_set_font(canvas, FontSecondary); + elements_button_left(canvas, "Retry"); +} + +bool avr_isp_chip_detect_view_input(InputEvent* event, void* context) { + furi_assert(context); + + AvrIspChipDetectView* instance = context; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyBack) { + return false; + } else if(event->key == InputKeyRight) { + with_view_model( + instance->view, + AvrIspChipDetectViewModel * model, + { + if(model->state == AvrIspChipDetectViewStateDetected) { + if(instance->callback) + instance->callback( + AvrIspCustomEventSceneChipDetectOk, instance->context); + } + }, + false); + + } else if(event->key == InputKeyLeft) { + bool detect_chip = false; + with_view_model( + instance->view, + AvrIspChipDetectViewModel * model, + { + if(model->state != AvrIspChipDetectViewStateDetecting) { + model->state = AvrIspChipDetectViewStateDetecting; + detect_chip = true; + } + }, + false); + if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw); + } + } else { + return false; + } + + return true; +} + +static void avr_isp_chip_detect_detect_chip_callback( + void* context, + const char* name, + bool detect_chip, + uint32_t flash_size) { + furi_assert(context); + + AvrIspChipDetectView* instance = context; + with_view_model( + instance->view, + AvrIspChipDetectViewModel * model, + { + model->name_chip = name; + model->flash_size = flash_size; + if(detect_chip) { + model->state = AvrIspChipDetectViewStateDetected; + } else { + model->state = AvrIspChipDetectViewStateNoDetect; + } + }, + true); +} +void avr_isp_chip_detect_view_enter(void* context) { + furi_assert(context); + + AvrIspChipDetectView* instance = context; + bool detect_chip = false; + with_view_model( + instance->view, + AvrIspChipDetectViewModel * model, + { + if(model->state == AvrIspChipDetectViewStateNoDetect || + model->state == AvrIspChipDetectViewStateDetected) { + detect_chip = true; + } + }, + false); + + //Start avr_isp_worker_rw + instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context); + + avr_isp_worker_rw_set_callback( + instance->avr_isp_worker_rw, avr_isp_chip_detect_detect_chip_callback, instance); + + if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw); +} + +void avr_isp_chip_detect_view_exit(void* context) { + furi_assert(context); + + AvrIspChipDetectView* instance = context; + + avr_isp_worker_rw_set_callback(instance->avr_isp_worker_rw, NULL, NULL); + avr_isp_worker_rw_free(instance->avr_isp_worker_rw); +} + +AvrIspChipDetectView* avr_isp_chip_detect_view_alloc() { + AvrIspChipDetectView* instance = malloc(sizeof(AvrIspChipDetectView)); + + // View allocation and configuration + instance->view = view_alloc(); + + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspChipDetectViewModel)); + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_chip_detect_view_draw); + view_set_input_callback(instance->view, avr_isp_chip_detect_view_input); + view_set_enter_callback(instance->view, avr_isp_chip_detect_view_enter); + view_set_exit_callback(instance->view, avr_isp_chip_detect_view_exit); + + with_view_model( + instance->view, + AvrIspChipDetectViewModel * model, + { model->state = AvrIspChipDetectViewStateNoDetect; }, + false); + return instance; +} + +void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance) { + furi_assert(instance); + + return instance->view; +} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h new file mode 100644 index 0000000000..37f2ae2336 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "../helpers/avr_isp_types.h" +#include "../helpers/avr_isp_event.h" + +typedef struct AvrIspChipDetectView AvrIspChipDetectView; + +typedef void (*AvrIspChipDetectViewCallback)(AvrIspCustomEvent event, void* context); + +typedef enum { + AvrIspChipDetectViewStateNoDetect, + AvrIspChipDetectViewStateDetecting, + AvrIspChipDetectViewStateDetected, + AvrIspChipDetectViewStateErrorOccured, + AvrIspChipDetectViewStateErrorVerification, +} AvrIspChipDetectViewState; + +void avr_isp_chip_detect_view_set_callback( + AvrIspChipDetectView* instance, + AvrIspChipDetectViewCallback callback, + void* context); + +void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state); + +AvrIspChipDetectView* avr_isp_chip_detect_view_alloc(); + +void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance); + +View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance); + +void avr_isp_chip_detect_view_exit(void* context); diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c new file mode 100644 index 0000000000..34e18770b2 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c @@ -0,0 +1,134 @@ +#include "avr_isp_view_programmer.h" +#include + +#include "../helpers/avr_isp_worker.h" +#include + +struct AvrIspProgrammerView { + View* view; + AvrIspWorker* worker; + AvrIspProgrammerViewCallback callback; + void* context; +}; + +typedef struct { + AvrIspProgrammerViewStatus status; +} AvrIspProgrammerViewModel; + +void avr_isp_programmer_view_set_callback( + AvrIspProgrammerView* instance, + AvrIspProgrammerViewCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_programmer_view_draw(Canvas* canvas, AvrIspProgrammerViewModel* model) { + canvas_clear(canvas); + + if(model->status == AvrIspProgrammerViewStatusUSBConnect) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_icon(canvas, 0, 0, &I_isp_active_128x53); + elements_multiline_text(canvas, 45, 10, "ISP mode active"); + } else { + canvas_set_font(canvas, FontSecondary); + canvas_draw_icon(canvas, 51, 6, &I_link_waiting_77x56); + elements_multiline_text(canvas, 0, 25, "Waiting for\nsoftware\nconnection"); + } +} + +bool avr_isp_programmer_view_input(InputEvent* event, void* context) { + furi_assert(context); + UNUSED(context); + + if(event->key == InputKeyBack || event->type != InputTypeShort) { + return false; + } + + return true; +} + +static void avr_isp_programmer_usb_connect_callback(void* context, bool status_connect) { + furi_assert(context); + AvrIspProgrammerView* instance = context; + + with_view_model( + instance->view, + AvrIspProgrammerViewModel * model, + { + if(status_connect) { + model->status = AvrIspProgrammerViewStatusUSBConnect; + } else { + model->status = AvrIspProgrammerViewStatusNoUSBConnect; + } + }, + true); +} + +void avr_isp_programmer_view_enter(void* context) { + furi_assert(context); + + AvrIspProgrammerView* instance = context; + with_view_model( + instance->view, + AvrIspProgrammerViewModel * model, + { model->status = AvrIspProgrammerViewStatusNoUSBConnect; }, + true); + + //Start worker + instance->worker = avr_isp_worker_alloc(instance->context); + + avr_isp_worker_set_callback( + instance->worker, avr_isp_programmer_usb_connect_callback, instance); + + avr_isp_worker_start(instance->worker); +} + +void avr_isp_programmer_view_exit(void* context) { + furi_assert(context); + + AvrIspProgrammerView* instance = context; + //Stop worker + if(avr_isp_worker_is_running(instance->worker)) { + avr_isp_worker_stop(instance->worker); + } + avr_isp_worker_set_callback(instance->worker, NULL, NULL); + avr_isp_worker_free(instance->worker); +} + +AvrIspProgrammerView* avr_isp_programmer_view_alloc() { + AvrIspProgrammerView* instance = malloc(sizeof(AvrIspProgrammerView)); + + // View allocation and configuration + instance->view = view_alloc(); + + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspProgrammerViewModel)); + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_programmer_view_draw); + view_set_input_callback(instance->view, avr_isp_programmer_view_input); + view_set_enter_callback(instance->view, avr_isp_programmer_view_enter); + view_set_exit_callback(instance->view, avr_isp_programmer_view_exit); + + with_view_model( + instance->view, + AvrIspProgrammerViewModel * model, + { model->status = AvrIspProgrammerViewStatusNoUSBConnect; }, + false); + return instance; +} + +void avr_isp_programmer_view_free(AvrIspProgrammerView* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance) { + furi_assert(instance); + + return instance->view; +} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h new file mode 100644 index 0000000000..9f005b026a --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include "../helpers/avr_isp_types.h" +#include "../helpers/avr_isp_event.h" + +typedef struct AvrIspProgrammerView AvrIspProgrammerView; + +typedef void (*AvrIspProgrammerViewCallback)(AvrIspCustomEvent event, void* context); + +typedef enum { + AvrIspProgrammerViewStatusNoUSBConnect, + AvrIspProgrammerViewStatusUSBConnect, +} AvrIspProgrammerViewStatus; + +void avr_isp_programmer_view_set_callback( + AvrIspProgrammerView* instance, + AvrIspProgrammerViewCallback callback, + void* context); + +AvrIspProgrammerView* avr_isp_programmer_view_alloc(); + +void avr_isp_programmer_view_free(AvrIspProgrammerView* instance); + +View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance); + +void avr_isp_programmer_view_exit(void* context); diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c new file mode 100644 index 0000000000..92d15bd7f8 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c @@ -0,0 +1,215 @@ +#include "avr_isp_view_reader.h" +#include + +#include "../helpers/avr_isp_worker_rw.h" + +struct AvrIspReaderView { + View* view; + AvrIspWorkerRW* avr_isp_worker_rw; + const char* file_path; + const char* file_name; + AvrIspReaderViewCallback callback; + void* context; +}; + +typedef struct { + AvrIspReaderViewStatus status; + float progress_flash; + float progress_eeprom; +} AvrIspReaderViewModel; + +void avr_isp_reader_update_progress(AvrIspReaderView* instance) { + with_view_model( + instance->view, + AvrIspReaderViewModel * model, + { + model->progress_flash = + avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw); + model->progress_eeprom = + avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw); + }, + true); +} + +void avr_isp_reader_view_set_callback( + AvrIspReaderView* instance, + AvrIspReaderViewCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_reader_set_file_path( + AvrIspReaderView* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; +} + +void avr_isp_reader_view_draw(Canvas* canvas, AvrIspReaderViewModel* model) { + canvas_clear(canvas); + char str_buf[64] = {0}; + + canvas_set_font(canvas, FontPrimary); + switch(model->status) { + case AvrIspReaderViewStatusIDLE: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to dump"); + canvas_set_font(canvas, FontSecondary); + elements_button_center(canvas, "Start"); + break; + case AvrIspReaderViewStatusReading: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Reading dump"); + break; + case AvrIspReaderViewStatusVerification: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifyng dump"); + break; + + default: + break; + } + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 27, "Flash"); + snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100)); + elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_buf); + canvas_draw_str(canvas, 0, 43, "EEPROM"); + snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_buf); +} + +bool avr_isp_reader_view_input(InputEvent* event, void* context) { + furi_assert(context); + AvrIspReaderView* instance = context; + + bool ret = true; + if(event->key == InputKeyBack && event->type == InputTypeShort) { + with_view_model( + instance->view, + AvrIspReaderViewModel * model, + { + if(model->status == AvrIspReaderViewStatusIDLE) { + if(instance->callback) + instance->callback(AvrIspCustomEventSceneExit, instance->context); + ret = false; + } + }, + false); + } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + with_view_model( + instance->view, + AvrIspReaderViewModel * model, + { + if(model->status == AvrIspReaderViewStatusIDLE) { + model->status = AvrIspReaderViewStatusReading; + avr_isp_worker_rw_read_dump_start( + instance->avr_isp_worker_rw, instance->file_path, instance->file_name); + } + }, + false); + } + return ret; +} + +static void avr_isp_reader_callback_status(void* context, AvrIspWorkerRWStatus status) { + furi_assert(context); + AvrIspReaderView* instance = context; + + with_view_model( + instance->view, + AvrIspReaderViewModel * model, + { + switch(status) { + case AvrIspWorkerRWStatusEndReading: + model->status = AvrIspReaderViewStatusVerification; + avr_isp_worker_rw_verification_start( + instance->avr_isp_worker_rw, instance->file_path, instance->file_name); + model->status = AvrIspReaderViewStatusVerification; + break; + case AvrIspWorkerRWStatusEndVerification: + if(instance->callback) + instance->callback(AvrIspCustomEventSceneReadingOk, instance->context); + break; + case AvrIspWorkerRWStatusErrorVerification: + if(instance->callback) + instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context); + break; + + default: + //AvrIspWorkerRWStatusErrorReading; + if(instance->callback) + instance->callback(AvrIspCustomEventSceneErrorReading, instance->context); + break; + } + }, + true); +} + +void avr_isp_reader_view_enter(void* context) { + furi_assert(context); + AvrIspReaderView* instance = context; + + with_view_model( + instance->view, + AvrIspReaderViewModel * model, + { + model->status = AvrIspReaderViewStatusIDLE; + model->progress_flash = 0.0f; + model->progress_eeprom = 0.0f; + }, + true); + + //Start avr_isp_worker_rw + instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context); + + avr_isp_worker_rw_set_callback_status( + instance->avr_isp_worker_rw, avr_isp_reader_callback_status, instance); + + avr_isp_worker_rw_start(instance->avr_isp_worker_rw); +} + +void avr_isp_reader_view_exit(void* context) { + furi_assert(context); + + AvrIspReaderView* instance = context; + //Stop avr_isp_worker_rw + if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) { + avr_isp_worker_rw_stop(instance->avr_isp_worker_rw); + } + + avr_isp_worker_rw_free(instance->avr_isp_worker_rw); +} + +AvrIspReaderView* avr_isp_reader_view_alloc() { + AvrIspReaderView* instance = malloc(sizeof(AvrIspReaderView)); + + // View allocation and configuration + instance->view = view_alloc(); + + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspReaderViewModel)); + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_reader_view_draw); + view_set_input_callback(instance->view, avr_isp_reader_view_input); + view_set_enter_callback(instance->view, avr_isp_reader_view_enter); + view_set_exit_callback(instance->view, avr_isp_reader_view_exit); + + return instance; +} + +void avr_isp_reader_view_free(AvrIspReaderView* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* avr_isp_reader_view_get_view(AvrIspReaderView* instance) { + furi_assert(instance); + + return instance->view; +} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h new file mode 100644 index 0000000000..44a4399481 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include "../helpers/avr_isp_types.h" +#include "../helpers/avr_isp_event.h" + +typedef struct AvrIspReaderView AvrIspReaderView; + +typedef void (*AvrIspReaderViewCallback)(AvrIspCustomEvent event, void* context); + +typedef enum { + AvrIspReaderViewStatusIDLE, + AvrIspReaderViewStatusReading, + AvrIspReaderViewStatusVerification, +} AvrIspReaderViewStatus; + +void avr_isp_reader_update_progress(AvrIspReaderView* instance); + +void avr_isp_reader_set_file_path( + AvrIspReaderView* instance, + const char* file_path, + const char* file_name); + +void avr_isp_reader_view_set_callback( + AvrIspReaderView* instance, + AvrIspReaderViewCallback callback, + void* context); + +AvrIspReaderView* avr_isp_reader_view_alloc(); + +void avr_isp_reader_view_free(AvrIspReaderView* instance); + +View* avr_isp_reader_view_get_view(AvrIspReaderView* instance); + +void avr_isp_reader_view_exit(void* context); diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c new file mode 100644 index 0000000000..a06b785355 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c @@ -0,0 +1,268 @@ +#include "avr_isp_view_writer.h" +#include + +#include "../helpers/avr_isp_worker_rw.h" +#include + +struct AvrIspWriterView { + View* view; + AvrIspWorkerRW* avr_isp_worker_rw; + const char* file_path; + const char* file_name; + AvrIspWriterViewCallback callback; + void* context; +}; + +typedef struct { + AvrIspWriterViewStatus status; + float progress_flash; + float progress_eeprom; +} AvrIspWriterViewModel; + +void avr_isp_writer_update_progress(AvrIspWriterView* instance) { + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + model->progress_flash = + avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw); + model->progress_eeprom = + avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw); + }, + true); +} + +void avr_isp_writer_view_set_callback( + AvrIspWriterView* instance, + AvrIspWriterViewCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_writer_set_file_path( + AvrIspWriterView* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; +} + +void avr_isp_writer_view_draw(Canvas* canvas, AvrIspWriterViewModel* model) { + canvas_clear(canvas); + char str_flash[32] = {0}; + char str_eeprom[32] = {0}; + + canvas_set_font(canvas, FontPrimary); + + switch(model->status) { + case AvrIspWriterViewStatusIDLE: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to write"); + canvas_set_font(canvas, FontSecondary); + elements_button_center(canvas, "Start"); + snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); + snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + break; + case AvrIspWriterViewStatusWriting: + if(float_is_equal(model->progress_flash, 0.f)) { + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying firmware"); + snprintf(str_flash, sizeof(str_flash), "***"); + snprintf(str_eeprom, sizeof(str_eeprom), "***"); + } else { + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing dump"); + snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); + snprintf( + str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + } + break; + case AvrIspWriterViewStatusVerification: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying dump"); + snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); + snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + break; + case AvrIspWriterViewStatusWritingFuse: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing fuse"); + snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); + snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + break; + case AvrIspWriterViewStatusWritingFuseOk: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Done!"); + snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); + snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + canvas_set_font(canvas, FontSecondary); + elements_button_center(canvas, "Reflash"); + elements_button_right(canvas, "Exit"); + break; + + default: + break; + } + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 27, "Flash"); + // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100)); + elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_flash); + canvas_draw_str(canvas, 0, 43, "EEPROM"); + // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_eeprom); +} + +bool avr_isp_writer_view_input(InputEvent* event, void* context) { + furi_assert(context); + AvrIspWriterView* instance = context; + + bool ret = true; + if(event->key == InputKeyBack && event->type == InputTypeShort) { + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + if((model->status == AvrIspWriterViewStatusIDLE) || + (model->status == AvrIspWriterViewStatusWritingFuseOk)) { + if(instance->callback) + instance->callback(AvrIspCustomEventSceneExit, instance->context); + ret = false; + } + }, + false); + } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + if((model->status == AvrIspWriterViewStatusIDLE) || + (model->status == AvrIspWriterViewStatusWritingFuseOk)) { + model->status = AvrIspWriterViewStatusWriting; + + avr_isp_worker_rw_write_dump_start( + instance->avr_isp_worker_rw, instance->file_path, instance->file_name); + } + }, + false); + } else if(event->key == InputKeyRight && event->type == InputTypeShort) { + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + if((model->status == AvrIspWriterViewStatusIDLE) || + (model->status == AvrIspWriterViewStatusWritingFuseOk)) { + if(instance->callback) + instance->callback(AvrIspCustomEventSceneExitStartMenu, instance->context); + ret = false; + } + }, + false); + } + return ret; +} + +static void avr_isp_writer_callback_status(void* context, AvrIspWorkerRWStatus status) { + furi_assert(context); + + AvrIspWriterView* instance = context; + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + switch(status) { + case AvrIspWorkerRWStatusEndWriting: + model->status = AvrIspWriterViewStatusVerification; + avr_isp_worker_rw_verification_start( + instance->avr_isp_worker_rw, instance->file_path, instance->file_name); + model->status = AvrIspWriterViewStatusVerification; + break; + case AvrIspWorkerRWStatusErrorVerification: + if(instance->callback) + instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context); + break; + case AvrIspWorkerRWStatusEndVerification: + avr_isp_worker_rw_write_fuse_start( + instance->avr_isp_worker_rw, instance->file_path, instance->file_name); + model->status = AvrIspWriterViewStatusWritingFuse; + break; + case AvrIspWorkerRWStatusErrorWritingFuse: + if(instance->callback) + instance->callback(AvrIspCustomEventSceneErrorWritingFuse, instance->context); + break; + case AvrIspWorkerRWStatusEndWritingFuse: + model->status = AvrIspWriterViewStatusWritingFuseOk; + break; + + default: + //AvrIspWorkerRWStatusErrorWriting; + if(instance->callback) + instance->callback(AvrIspCustomEventSceneErrorWriting, instance->context); + break; + } + }, + true); +} + +void avr_isp_writer_view_enter(void* context) { + furi_assert(context); + + AvrIspWriterView* instance = context; + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + model->status = AvrIspWriterViewStatusIDLE; + model->progress_flash = 0.0f; + model->progress_eeprom = 0.0f; + }, + true); + + //Start avr_isp_worker_rw + instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context); + + avr_isp_worker_rw_set_callback_status( + instance->avr_isp_worker_rw, avr_isp_writer_callback_status, instance); + + avr_isp_worker_rw_start(instance->avr_isp_worker_rw); +} + +void avr_isp_writer_view_exit(void* context) { + furi_assert(context); + AvrIspWriterView* instance = context; + + //Stop avr_isp_worker_rw + if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) { + avr_isp_worker_rw_stop(instance->avr_isp_worker_rw); + } + + avr_isp_worker_rw_free(instance->avr_isp_worker_rw); +} + +AvrIspWriterView* avr_isp_writer_view_alloc() { + AvrIspWriterView* instance = malloc(sizeof(AvrIspWriterView)); + + // View allocation and configuration + instance->view = view_alloc(); + + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspWriterViewModel)); + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_writer_view_draw); + view_set_input_callback(instance->view, avr_isp_writer_view_input); + view_set_enter_callback(instance->view, avr_isp_writer_view_enter); + view_set_exit_callback(instance->view, avr_isp_writer_view_exit); + + return instance; +} + +void avr_isp_writer_view_free(AvrIspWriterView* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* avr_isp_writer_view_get_view(AvrIspWriterView* instance) { + furi_assert(instance); + + return instance->view; +} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h new file mode 100644 index 0000000000..1ff7283876 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include "../helpers/avr_isp_types.h" +#include "../helpers/avr_isp_event.h" + +typedef struct AvrIspWriterView AvrIspWriterView; + +typedef void (*AvrIspWriterViewCallback)(AvrIspCustomEvent event, void* context); + +typedef enum { + AvrIspWriterViewStatusIDLE, + AvrIspWriterViewStatusWriting, + AvrIspWriterViewStatusVerification, + AvrIspWriterViewStatusWritingFuse, + AvrIspWriterViewStatusWritingFuseOk, +} AvrIspWriterViewStatus; + +void avr_isp_writer_update_progress(AvrIspWriterView* instance); + +void avr_isp_writer_set_file_path( + AvrIspWriterView* instance, + const char* file_path, + const char* file_name); + +void avr_isp_writer_view_set_callback( + AvrIspWriterView* instance, + AvrIspWriterViewCallback callback, + void* context); + +AvrIspWriterView* avr_isp_writer_view_alloc(); + +void avr_isp_writer_view_free(AvrIspWriterView* instance); + +View* avr_isp_writer_view_get_view(AvrIspWriterView* instance); + +void avr_isp_writer_view_exit(void* context); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 7713beb93b..3806ac47b0 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -158,6 +158,7 @@ Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, Header,+,lib/toolbox/float_tools.h,, +Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, @@ -1309,6 +1310,10 @@ Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,+,hal_sd_detect,_Bool, Function,+,hal_sd_detect_init,void, Function,+,hal_sd_detect_set_low,void, +Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" +Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" +Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" +Function,+,hex_chars_to_uint8,_Bool,"const char*, uint8_t*" Function,+,icon_animation_alloc,IconAnimation*,const Icon* Function,+,icon_animation_free,void,IconAnimation* Function,+,icon_animation_get_height,uint8_t,const IconAnimation* @@ -1870,6 +1875,7 @@ Function,+,uECC_sign,int,"const uint8_t*, const uint8_t*, unsigned, uint8_t*, uE Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned, const uECC_HashContext*, uint8_t*, uECC_Curve" Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" +Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index fea7926801..efc20b4a74 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -190,6 +190,7 @@ Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, Header,+,lib/toolbox/float_tools.h,, +Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, @@ -1597,6 +1598,10 @@ Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,+,hal_sd_detect,_Bool, Function,+,hal_sd_detect_init,void, Function,+,hal_sd_detect_set_low,void, +Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" +Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" +Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" +Function,+,hex_chars_to_uint8,_Bool,"const char*, uint8_t*" Function,-,hypot,double,"double, double" Function,-,hypotf,float,"float, float" Function,-,hypotl,long double,"long double, long double" @@ -2801,6 +2806,7 @@ Function,+,uECC_sign,int,"const uint8_t*, const uint8_t*, unsigned, uint8_t*, uE Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned, const uECC_HashContext*, uint8_t*, uECC_Curve" Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" +Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index fad4c55841..bb06c2db4b 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -28,6 +28,7 @@ env.Append( File("stream/buffered_file_stream.h"), File("protocols/protocol_dict.h"), File("pretty_format.h"), + File("hex.h"), ], ) From 53f6ae7a39bc726dc57c84ae19b2bd34356dbbe6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Apr 2023 07:20:56 +0300 Subject: [PATCH 22/32] Fix after-merge issues --- applications/main/subghz_remote/subghz_remote_app.c | 6 +++--- firmware/targets/f7/api_symbols.csv | 1 + scripts/sconsdist.py | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/applications/main/subghz_remote/subghz_remote_app.c b/applications/main/subghz_remote/subghz_remote_app.c index 9e4e23152e..0402738387 100644 --- a/applications/main/subghz_remote/subghz_remote_app.c +++ b/applications/main/subghz_remote/subghz_remote_app.c @@ -700,15 +700,15 @@ static void render_callback(Canvas* canvas, void* ctx) { break; case 2: canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 116, 17, &I_Pin_arrow_down_7x9); + canvas_draw_icon_ex(canvas, 116, 17, &I_Pin_arrow_up_7x9, IconRotation180); break; case 3: canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 115, 18, &I_Pin_arrow_right_9x7); + canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation90); break; case 4: canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 115, 18, &I_Pin_arrow_left_9x7); + canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation270); break; case 5: canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index d106f40d89..10cf2ccd1f 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -638,6 +638,7 @@ Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, Function,+,canvas_draw_str,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection" +Function,+,canvas_draw_u8g2_bitmap,void,"u8g2_t*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*, uint8_t" Function,+,canvas_draw_xbm,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" Function,-,canvas_frame_set,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,-,canvas_free,void,Canvas* diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index ee671004a3..382b00f691 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -259,8 +259,7 @@ def bundle_update_package(self): or "" ) if (log_custom_fz_name != "") and (len(log_custom_fz_name) <= 8) and (log_custom_fz_name.isalnum()) and (log_custom_fz_name.isascii()): - self.logger.info(f"Flipper Custom Name is set:\n\tName: {log_custom_fz_name} : length - {len(log_custom_fz_name)} chars" - ) + self.logger.info(f"Flipper Custom Name is set:\n\tName: {log_custom_fz_name} : length - {len(log_custom_fz_name)} chars") if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: self.note_dist_component("update", "dir", bundle_dir) From 51d52c213ba340906164848f71b977b7e9819784 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Apr 2023 07:28:19 +0300 Subject: [PATCH 23/32] Comment debug logs back just in case, to avoid extra calls while reading flash --- .../external/avr_isp_programmer/helpers/avr_isp_worker_rw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c index fc8d3b09fe..f586e16452 100644 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c @@ -338,12 +338,12 @@ static void avr_isp_worker_rw_get_dump_flash(AvrIspWorkerRW* instance, const cha sizeof(data)); flipper_i32hex_file_bin_to_i32hex_set_data( flipper_hex_flash, data, avr_isp_chip_arr[instance->chip_arr_ind].pagesize); - FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash)); + //FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash)); instance->progress_flash = (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f); } flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_flash); - FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash)); + //FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash)); flipper_i32hex_file_close(flipper_hex_flash); instance->progress_flash = 1.0f; } From 2e32d716aa3bf865eb847fb05f301205266ba9b3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Apr 2023 07:41:06 +0300 Subject: [PATCH 24/32] Update changelog --- CHANGELOG.md | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 068cd01bd5..fb952bf97c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,14 @@ ### New changes -* SubGHz: Support for Allmatic(reads as Beninca), also fix Beninca protocol -* SubGHz: Allmatic -> Add Manually -* Plugins: Show External CC1101 module status in Weather Station and in POCSAG Pager plugins -* Plugins: Update **TOTP (Authenticator)** [(by akopachov)](https://github.com/akopachov/flipper-zero_authenticator) (Fixes BT issues) -* SubGHz: Fix false detections of StarLine 72bit, flipper can decode only 64bit -* SubGHz: ScherKhan, more informative messages instead of "Unknown", may be not correct, because we guessing type by bits -* SubGHz: Clear code in "Add Manually" scene (by @gid9798 | PR #403) -* SubGHz: Unification of custom buttons (by @gid9798 | PR #405) -* Infrared: Universal remote assets updated (by @amec0e | PR #404) -* Plugins: GPS NMEA (UART) modifications -``` -- Ability to change baudrate using Up button, hold button to switch between baudrates (9600, 57600, 115200) (i set 57600 as default) -- Ok button will set backlight to always on mode, to disable press ok button again (it will restore default settings after app exit too) -- Long press Right button to change speed from knots to kilometers per hour -- Exit from app using long press on back button instead of short press, may be useful in case you want to turn backlight on and accidentally click back -``` -* OFW PR 2536: Fixing parsing troika card number (by @achistyakov) -* OFW PR 2548: Picopass Add more detail to saved info screen (by @bettse) -* OFW PR 2544: added WAIT_FOR_BUTTON_PRESS functionality to badusb (by @p4p1) -* OFW PR 2475: AVR ISP Programmer FAP (by @Skorpionm) (+ AVR ISP enable 5v power on start fix) (beta test, report found issues in our repo) -* OFW: FatFS: use rtc for timestamping -* OFW: RPC: increase max message size -* OFW: Picopass: Elite progress -* OFW: Improved thread lifecycle -* OFW: View Model: recursive mutex +* SubGHz: AN-Motors AT4 - Add manually fixes +* SubGHz: StarLine ignore option (in Read -> Config) (by @gid9798 | PR #410) +* Plugins: Update **TOTP (Authenticator)** [(by akopachov)](https://github.com/akopachov/flipper-zero_authenticator) (Steam guard support) +* Plugins: Update **UART Terminal** [(by cool4uma)](https://github.com/cool4uma/UART_Terminal/tree/main) (AT commands support) +* Plugins: Add IR Scope app. (by @kallanreed | PR #407) +* OFW: Graphics cleanup and icon rotation +* OFW: Moved ufbt to fbt codebase +* OFW: SD Driver: do not cache sd status. +* OFW: Furi: more gpio checks in HAL +* OFW: WeatherStation: fix protocol TX141TH-BV2 #### [🎲 Download latest extra apps pack](https://github.com/xMasterX/all-the-plugins/archive/refs/heads/main.zip) From a116dbc35853dc55f90118e70914b316afc71052 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Apr 2023 23:25:30 +0300 Subject: [PATCH 25/32] Revert checks temporarily --- firmware/targets/f7/furi_hal/furi_hal_gpio.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_gpio.c b/firmware/targets/f7/furi_hal/furi_hal_gpio.c index afd482f2f5..e8318afcf5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_gpio.c +++ b/firmware/targets/f7/furi_hal/furi_hal_gpio.c @@ -53,8 +53,8 @@ void furi_hal_gpio_init( const GpioPull pull, const GpioSpeed speed) { // we cannot set alternate mode in this function - furi_check(mode != GpioModeAltFunctionPushPull); - furi_check(mode != GpioModeAltFunctionOpenDrain); + furi_assert(mode != GpioModeAltFunctionPushPull); + furi_assert(mode != GpioModeAltFunctionOpenDrain); furi_hal_gpio_init_ex(gpio, mode, pull, speed, GpioAltFnUnused); } @@ -178,7 +178,7 @@ void furi_hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, vo FURI_CRITICAL_ENTER(); uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); - furi_check(gpio_interrupt[pin_num].callback == NULL); + furi_assert(gpio_interrupt[pin_num].callback == NULL); gpio_interrupt[pin_num].callback = cb; gpio_interrupt[pin_num].context = ctx; gpio_interrupt[pin_num].ready = true; From 011c0b191460db49e50f7bd798d4a0ae1fd3e0eb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Apr 2023 23:45:35 +0300 Subject: [PATCH 26/32] Revert nfc changes too --- lib/ST25RFAL002/platform.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/ST25RFAL002/platform.c b/lib/ST25RFAL002/platform.c index 5fe65ef880..754e256501 100644 --- a/lib/ST25RFAL002/platform.c +++ b/lib/ST25RFAL002/platform.c @@ -45,19 +45,16 @@ void platformDisableIrqCallback() { void platformSetIrqCallback(PlatformIrqCallback callback) { rfal_platform.callback = callback; - - if(!rfal_platform.thread) { - rfal_platform.thread = - furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL); - furi_thread_mark_as_service(rfal_platform.thread); - furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr); - furi_thread_start(rfal_platform.thread); - - furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, nfc_isr, NULL); - // Disable interrupt callback as the pin is shared between 2 apps - // It is enabled in rfalLowPowerModeStop() - furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull); - } + rfal_platform.thread = + furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL); + furi_thread_mark_as_service(rfal_platform.thread); + furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr); + furi_thread_start(rfal_platform.thread); + + furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, nfc_isr, NULL); + // Disable interrupt callback as the pin is shared between 2 apps + // It is enabled in rfalLowPowerModeStop() + furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull); } bool platformSpiTxRx(const uint8_t* txBuf, uint8_t* rxBuf, uint16_t len) { From a5a95847ea1d95f7770b9ab62b5e5d123c7905b4 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 6 Apr 2023 23:37:12 +0400 Subject: [PATCH 27/32] scripts: sconsdist: added stub file artifact for older ufbt (#2568) * scripts: sconsdist: added stub file artifact for older ufbt * scripts: sconsdist: not creating dummy SDK archive --- scripts/sconsdist.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 382b00f691..2d5b1ae798 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -84,21 +84,16 @@ def copy_single_project(self, project: ProjectDir) -> None: if exists(sdk_folder := join(obj_directory, foldertype)): self.note_dist_component(foldertype, "dir", sdk_folder) - def package_zip(self, foldertype, sdk_folder): + # TODO: remove this after everyone migrates to new uFBT + self.create_zip_stub("lib") + + def create_zip_stub(self, foldertype): with zipfile.ZipFile( self.get_dist_path(self.get_dist_file_name(foldertype, "zip")), "w", zipfile.ZIP_DEFLATED, - ) as zf: - for root, _, files in walk(sdk_folder): - for file in files: - zf.write( - join(root, file), - relpath( - join(root, file), - sdk_folder, - ), - ) + ) as _: + pass def copy(self) -> int: self._dist_components: dict[str, str] = dict() From bf29f55205050d7629552b48c92588ff4a01aafe Mon Sep 17 00:00:00 2001 From: SkorP Date: Thu, 6 Apr 2023 14:56:17 +0400 Subject: [PATCH 28/32] ISP: fix state gpio ISP Programmer --- applications/external/avr_isp_programmer/helpers/avr_isp.c | 1 + .../external/avr_isp_programmer/helpers/avr_isp_worker_rw.c | 3 +-- .../external/avr_isp_programmer/lib/driver/avr_isp_prog.c | 1 + .../external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c | 2 -- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.c b/applications/external/avr_isp_programmer/helpers/avr_isp.c index 76e0a80b06..51b4f8846a 100644 --- a/applications/external/avr_isp_programmer/helpers/avr_isp.c +++ b/applications/external/avr_isp_programmer/helpers/avr_isp.c @@ -152,6 +152,7 @@ bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance) { } } } + if(instance->spi) avr_isp_spi_sw_free(instance->spi); return false; } diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c index f586e16452..30af201e1b 100644 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c @@ -198,9 +198,8 @@ bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) { } avr_isp_end_pmode(instance->avr_isp); - furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); - } while(0); + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4) ; if(instance->callback) { if(instance->chip_arr_ind > avr_isp_chip_arr_size) { instance->callback(instance->context, "No detect", instance->chip_detect, 0); diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c index b457e4c27b..b3c81f3b13 100644 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c @@ -317,6 +317,7 @@ static bool avr_isp_prog_auto_set_spi_speed_start_pmode(AvrIspProg* instance) { } } } + if(instance->spi) avr_isp_spi_sw_free(instance->spi); return false; } diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c index f60850c841..c6d9d54c89 100644 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c @@ -18,7 +18,6 @@ struct AvrIspSpiSw { AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed) { AvrIspSpiSw* instance = malloc(sizeof(AvrIspSpiSw)); instance->speed_wait_time = speed; - instance->miso = AVR_ISP_SPI_SW_MISO; instance->mosi = AVR_ISP_SPI_SW_MOSI; instance->sck = AVR_ISP_SPI_SW_SCK; @@ -40,7 +39,6 @@ void avr_isp_spi_sw_free(AvrIspSpiSw* instance) { furi_hal_gpio_init(instance->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(instance->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(instance->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - free(instance); } From b9bc34afbf2edc80cab6277009818a3d90432abe Mon Sep 17 00:00:00 2001 From: SkorP Date: Thu, 6 Apr 2023 14:58:20 +0400 Subject: [PATCH 29/32] WS: delete string debug --- .../external/weather_station/protocols/lacrosse_tx141thbv2.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c index 33a61cee06..f2fddd40c7 100644 --- a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c +++ b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c @@ -217,11 +217,6 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) { - FURI_LOG_E( - "WS", - "%llX %d", - instance->decoder.decode_data, - instance->decoder.decode_count_bit); if((instance->decoder.decode_count_bit == ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) || (instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT)) { From 9de6b6355e5f781c5593d5bd25ac4a259d68077b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Apr 2023 23:50:34 +0300 Subject: [PATCH 30/32] format --- .../external/avr_isp_programmer/helpers/avr_isp_worker_rw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c index 30af201e1b..d64cfdbdf1 100644 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c @@ -199,7 +199,7 @@ bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) { avr_isp_end_pmode(instance->avr_isp); } while(0); - furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4) ; + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); if(instance->callback) { if(instance->chip_arr_ind > avr_isp_chip_arr_size) { instance->callback(instance->context, "No detect", instance->chip_detect, 0); From b8dba0fd0c7119950e31edcedcb11dfe6c6dcb11 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Apr 2023 23:56:48 +0300 Subject: [PATCH 31/32] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb952bf97c..c741e12f69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * Plugins: Update **TOTP (Authenticator)** [(by akopachov)](https://github.com/akopachov/flipper-zero_authenticator) (Steam guard support) * Plugins: Update **UART Terminal** [(by cool4uma)](https://github.com/cool4uma/UART_Terminal/tree/main) (AT commands support) * Plugins: Add IR Scope app. (by @kallanreed | PR #407) +* OFW PR 2567: Fix state gpio AVR Programmer (by @Skorpionm) +* OFW: scripts: sconsdist: added stub file artifact for older ufbt * OFW: Graphics cleanup and icon rotation * OFW: Moved ufbt to fbt codebase * OFW: SD Driver: do not cache sd status. From 5a7cddfb690ad5375ab2bd03d94f72274fc24bab Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 7 Apr 2023 00:07:12 +0300 Subject: [PATCH 32/32] revert changes / update changelog --- CHANGELOG.md | 1 - applications/external/avr_isp_programmer/helpers/avr_isp.c | 1 - .../external/avr_isp_programmer/helpers/avr_isp_worker_rw.c | 3 ++- .../external/avr_isp_programmer/lib/driver/avr_isp_prog.c | 1 - .../external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c | 2 ++ 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c741e12f69..7fdb0ef795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ * Plugins: Update **TOTP (Authenticator)** [(by akopachov)](https://github.com/akopachov/flipper-zero_authenticator) (Steam guard support) * Plugins: Update **UART Terminal** [(by cool4uma)](https://github.com/cool4uma/UART_Terminal/tree/main) (AT commands support) * Plugins: Add IR Scope app. (by @kallanreed | PR #407) -* OFW PR 2567: Fix state gpio AVR Programmer (by @Skorpionm) * OFW: scripts: sconsdist: added stub file artifact for older ufbt * OFW: Graphics cleanup and icon rotation * OFW: Moved ufbt to fbt codebase diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.c b/applications/external/avr_isp_programmer/helpers/avr_isp.c index 51b4f8846a..76e0a80b06 100644 --- a/applications/external/avr_isp_programmer/helpers/avr_isp.c +++ b/applications/external/avr_isp_programmer/helpers/avr_isp.c @@ -152,7 +152,6 @@ bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance) { } } } - if(instance->spi) avr_isp_spi_sw_free(instance->spi); return false; } diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c index d64cfdbdf1..f586e16452 100644 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c @@ -198,8 +198,9 @@ bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) { } avr_isp_end_pmode(instance->avr_isp); + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + } while(0); - furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); if(instance->callback) { if(instance->chip_arr_ind > avr_isp_chip_arr_size) { instance->callback(instance->context, "No detect", instance->chip_detect, 0); diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c index b3c81f3b13..b457e4c27b 100644 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c @@ -317,7 +317,6 @@ static bool avr_isp_prog_auto_set_spi_speed_start_pmode(AvrIspProg* instance) { } } } - if(instance->spi) avr_isp_spi_sw_free(instance->spi); return false; } diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c index c6d9d54c89..f60850c841 100644 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c @@ -18,6 +18,7 @@ struct AvrIspSpiSw { AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed) { AvrIspSpiSw* instance = malloc(sizeof(AvrIspSpiSw)); instance->speed_wait_time = speed; + instance->miso = AVR_ISP_SPI_SW_MISO; instance->mosi = AVR_ISP_SPI_SW_MOSI; instance->sck = AVR_ISP_SPI_SW_SCK; @@ -39,6 +40,7 @@ void avr_isp_spi_sw_free(AvrIspSpiSw* instance) { furi_hal_gpio_init(instance->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(instance->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(instance->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + free(instance); }