Просмотр исходного кода

Hex mode & bug fixes

- Hex mode added (both for receiving and sending data)
- UART pins selection added (from 13 & 14 or 15 & 16 pins)
- UART settings are placed in a separate submenu
- Input from uart to help window fixed
ElectronicsInFocus 2 лет назад
Родитель
Сommit
86225cda58

+ 23 - 9
README.md

@@ -6,17 +6,20 @@
 | [![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
-- Send commands by uart
-- Set baud rate
-- Fast commands
+- Reading from UART in text or hex mode
+- Sending commands
+- Sending AT commands
+- Sending fast commands
+- Sending binary packets (in hex)
+- Baudrate selection
+- UART pins selection (2 options)
 
 ## Connecting
-| Flipper Zero pin | UART interface  |
-| ---------------- | --------------- |
-| 13 TX            | RX              |
-| 14 RX            | TX              |
-|8, 18 GND         | GND             |
+| Device UART interface  | Flipper Zero pin (default) | Flipper Zero pin (option) |
+| :----------------: | :---------------: | :---------------: |
+| RX | 13 TX | 15 TX |
+| TX | 14 RX | 16 TX |
+| GND  | 8, 18 GND | 8, 18 GND |
 
 Info: If possible, do not power your devices from 3V3 (pin 9) Flipper Zero. It does not support hot plugging.
 
@@ -27,6 +30,17 @@ To accommodate more characters on a small display, some characters are called up
 
 ![kbf](https://user-images.githubusercontent.com/122148894/212286637-7063f1ee-c6ff-46b9-8dc5-79a5f367fab1.png)
 
+## Supported send AT commands
+In the "Send AT command" mode, the keyboard settings are changed for the convenience of entering AT commands.
+
+![AT](https://user-images.githubusercontent.com/122148894/230785072-319fe5c9-deca-49f9-bfe4-5ace89d38d53.png)
+
+## Hex mode
+Hexadecimal mode is useful for working with simple binary protocols.
+You can see the bytes being received in hexadecimal in the terminal window.
+You can also send binary packets to UART. A simplified keyboard is used to enter packet bytes.
+
+![004](https://github.com/rnadyrshin/UART_Terminal/assets/110516632/d5d3c09b-accc-4e6f-b777-81e751284108)
 
 ## How to install
 Copy the contents of the repository to the applications_user/uart_terminal folder Flipper Zero firmware and build app with the command ./fbt fap_uart_terminal.

+ 1 - 1
application.fam

@@ -10,6 +10,6 @@ App(
     fap_category="GPIO",
     fap_icon_assets="assets",
     fap_author="@cool4uma & (some fixes by @xMasterX)",
-    fap_version="1.2",
+    fap_version="1.3",
     fap_description="Control various devices via the Flipper Zero UART interface.",
 )

+ 6 - 0
scenes/uart_terminal_scene.h

@@ -27,3 +27,9 @@ extern const SceneManagerHandlers uart_terminal_scene_handlers;
 #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
 #include "uart_terminal_scene_config.h"
 #undef ADD_SCENE
+
+#define UART_PINS_ITEM_IDX (0)
+#define BAUDRATE_ITEM_IDX (1)
+#define HEX_MODE_ITEM_IDX (2)
+
+#define DEFAULT_BAUDRATE_OPT_IDX (18)

+ 3 - 0
scenes/uart_terminal_scene_config.h

@@ -1,3 +1,6 @@
 ADD_SCENE(uart_terminal, start, Start)
+ADD_SCENE(uart_terminal, setup, Setup)
 ADD_SCENE(uart_terminal, console_output, ConsoleOutput)
 ADD_SCENE(uart_terminal, text_input, UART_TextInput)
+ADD_SCENE(uart_terminal, hex_input, UART_HexInput)
+ADD_SCENE(uart_terminal, help, Help)

+ 79 - 154
scenes/uart_terminal_scene_console_output.c

@@ -3,177 +3,77 @@
 void uart_terminal_console_output_handle_rx_data_cb(uint8_t* buf, size_t len, void* context) {
     furi_assert(context);
     UART_TerminalApp* app = context;
+    FuriString* new_str = furi_string_alloc();
+
+    if(app->hex_mode) {
+        while (len--) {
+            uint8_t byte = *(buf++);
+            if(byte == '\0') break;
+            furi_string_cat_printf(new_str, "%02X ", byte);
+        }
+    }
+    else {
+        buf[len] = '\0';
+        furi_string_cat_printf(new_str, "%s", buf);
+    }
 
     // If text box store gets too big, then truncate it
-    app->text_box_store_strlen += len;
-    if(app->text_box_store_strlen >= UART_TERMINAL_TEXT_BOX_STORE_SIZE - 1) {
+    app->text_box_store_strlen += furi_string_size(new_str);;
+    while(app->text_box_store_strlen >= UART_TERMINAL_TEXT_BOX_STORE_SIZE - 1) {
         furi_string_right(app->text_box_store, app->text_box_store_strlen / 2);
         app->text_box_store_strlen = furi_string_size(app->text_box_store) + len;
     }
 
-    // Null-terminate buf and append to text box store
-    buf[len] = '\0';
-    furi_string_cat_printf(app->text_box_store, "%s", buf);
+    furi_string_cat(app->text_box_store, new_str);
+    furi_string_free(new_str);
 
     view_dispatcher_send_custom_event(
         app->view_dispatcher, UART_TerminalEventRefreshConsoleOutput);
 }
 
+static uint8_t hex_char_to_byte(const char c) {
+    if(c >= '0' && c <= '9') {
+        return c-'0';
+    }
+    if(c >= 'A' && c <= 'F') {
+        return c-'A'+10;
+    }
+    if (c >= 'a' && c <= 'f') {
+        return c-'a'+10;
+    }
+    return 0;
+}
+
 void uart_terminal_scene_console_output_on_enter(void* context) {
     UART_TerminalApp* app = context;
 
     TextBox* text_box = app->text_box;
     text_box_reset(app->text_box);
     text_box_set_font(text_box, TextBoxFontText);
-    if(app->focus_console_start) {
-        text_box_set_focus(text_box, TextBoxFocusStart);
-    } else {
-        text_box_set_focus(text_box, TextBoxFocusEnd);
-    }
+    text_box_set_focus(text_box, TextBoxFocusEnd);
 
-    //Change baudrate ///////////////////////////////////////////////////////////////////////////
-    if(0 == strncmp("75", app->selected_tx_string, strlen("75")) && app->BAUDRATE != 75) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 75;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("110", app->selected_tx_string, strlen("110")) && app->BAUDRATE != 110) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 110;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("150", app->selected_tx_string, strlen("150")) && app->BAUDRATE != 150) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 150;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("300", app->selected_tx_string, strlen("300")) && app->BAUDRATE != 300) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 300;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("600", app->selected_tx_string, strlen("600")) && app->BAUDRATE != 600) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 600;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("1200", app->selected_tx_string, strlen("1200")) && app->BAUDRATE != 1200) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 1200;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("1800", app->selected_tx_string, strlen("1800")) && app->BAUDRATE != 1800) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 1800;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("2400", app->selected_tx_string, strlen("2400")) && app->BAUDRATE != 2400) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 2400;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("4800", app->selected_tx_string, strlen("4800")) && app->BAUDRATE != 4800) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 4800;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("7200", app->selected_tx_string, strlen("7200")) && app->BAUDRATE != 7200) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 7200;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("9600", app->selected_tx_string, strlen("9600")) && app->BAUDRATE != 9600) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 9600;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("14400", app->selected_tx_string, strlen("14400")) && app->BAUDRATE != 14400) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 14400;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("19200", app->selected_tx_string, strlen("19200")) && app->BAUDRATE != 19200) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 19200;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("38400", app->selected_tx_string, strlen("38400")) && app->BAUDRATE != 38400) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 38400;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("56000", app->selected_tx_string, strlen("56000")) && app->BAUDRATE != 56000) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 56000;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("57600", app->selected_tx_string, strlen("57600")) && app->BAUDRATE != 57600) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 57600;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("76800", app->selected_tx_string, strlen("76800")) && app->BAUDRATE != 76800) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 76800;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("115200", app->selected_tx_string, strlen("115200")) &&
-       app->BAUDRATE != 115200) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 115200;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("128000", app->selected_tx_string, strlen("128000")) && app->BAUDRATE != 128000) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 128000;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("230400", app->selected_tx_string, strlen("230400")) &&
-       app->BAUDRATE != 230400) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 230400;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("250000", app->selected_tx_string, strlen("250000")) && app->BAUDRATE != 250000) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 250000;
-        app->uart = uart_terminal_uart_init(app);
-    }
-    if(0 == strncmp("256000", app->selected_tx_string, strlen("256000")) && app->BAUDRATE != 256000) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 256000;
-        app->uart = uart_terminal_uart_init(app);
+    bool need_reinit = false;
+
+    //Change baudrate
+    if(app->BAUDRATE != app->NEW_BAUDRATE && app->NEW_BAUDRATE) {
+        need_reinit = true;
     }
-    if(0 == strncmp("460800", app->selected_tx_string, strlen("460800")) &&
-       app->BAUDRATE != 460800) {
-        uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 460800;
-        app->uart = uart_terminal_uart_init(app);
+
+    //Change UART port
+    if(app->uart_ch != app->new_uart_ch) {
+        need_reinit = true;
     }
-    if(0 == strncmp("921600", app->selected_tx_string, strlen("921600")) &&
-       app->BAUDRATE != 921600) {
+
+    if(need_reinit) {
         uart_terminal_uart_free(app->uart);
-        app->BAUDRATE = 921600;
+        app->BAUDRATE = app->NEW_BAUDRATE;
+        app->uart_ch = app->new_uart_ch;
         app->uart = uart_terminal_uart_init(app);
     }
-    /////////////////////////////////////////////////////////////////////////////////////////////////////
 
     if(app->is_command) {
         furi_string_reset(app->text_box_store);
         app->text_box_store_strlen = 0;
-
-        if(0 == strncmp("help", app->selected_tx_string, strlen("help"))) {
-            const char* help_msg =
-                "UART terminal for Flipper\n\nI'm in github: cool4uma\n\nThis app is a modified\nWiFi Marauder companion,\nThanks 0xchocolate(github)\nfor great code and app.\n\n";
-            furi_string_cat_str(app->text_box_store, help_msg);
-            app->text_box_store_strlen += strlen(help_msg);
-        }
-
-        if(app->show_stopscan_tip) {
-            const char* help_msg = "Press BACK to return\n";
-            furi_string_cat_str(app->text_box_store, help_msg);
-            app->text_box_store_strlen += strlen(help_msg);
-        }
     }
 
     // Set starting text - for "View Log", this will just be what was already in the text box store
@@ -186,16 +86,41 @@ 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 CR+LF or newline '\n'
-    if(app->is_command && app->selected_tx_string) {
-        if(app->TERMINAL_MODE == 1){
-            uart_terminal_uart_tx(
-                (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string));
-            uart_terminal_uart_tx((uint8_t*)("\r\n"), 2);
-        } else {
-            uart_terminal_uart_tx(
-                (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string));
-            uart_terminal_uart_tx((uint8_t*)("\n"), 1);
+    uint8_t uart_ch = app->uart_ch;
+
+    if(app->hex_mode) {
+        // Send binary packet
+        if(app->selected_tx_string) {
+            const char *str = app->selected_tx_string;
+            uint8_t digit_num = 0;
+            uint8_t byte = 0;
+            while (*str) {
+                byte |= (hex_char_to_byte(*str) << ((1 - digit_num) * 4));
+
+                if(++digit_num == 2) {
+                    uart_terminal_uart_tx(uart_ch, &byte, 1);
+                    digit_num = 0;
+                    byte = 0;
+                }
+                str++;
+            }
+
+            if(digit_num) {
+                uart_terminal_uart_tx(uart_ch, &byte, 1);
+            }
+        }
+    } else {
+        // Send command with CR+LF or newline '\n'
+        if(app->is_command && app->selected_tx_string) {
+            if(app->TERMINAL_MODE == 1){
+                uart_terminal_uart_tx(uart_ch, 
+                    (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string));
+                uart_terminal_uart_tx(uart_ch, (uint8_t*)("\r\n"), 2);
+            } else {
+                uart_terminal_uart_tx(uart_ch, 
+                    (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string));
+                uart_terminal_uart_tx(uart_ch, (uint8_t*)("\n"), 1);
+            }
         }
     }
 }

+ 56 - 0
scenes/uart_terminal_scene_help.c

@@ -0,0 +1,56 @@
+#include "../uart_terminal_app_i.h"
+
+void uart_terminal_scene_help_widget_callback(GuiButtonType result, InputType type, void* context) {
+    UART_TerminalApp* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+void uart_terminal_scene_help_on_enter(void* context) {
+    UART_TerminalApp* app = context;
+
+    FuriString* temp_str;
+    temp_str = furi_string_alloc();
+    furi_string_printf(temp_str, "\nUART terminal for Flipper\n\nI'm in github: cool4uma\n\nThis app is a modified\nWiFi Marauder companion,\nThanks 0xchocolate(github)\nfor great code and app.\n\n");
+    furi_string_cat_printf( temp_str, "Press BACK to return\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!         UART Terminal            \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, UART_TerminalAppViewHelp);
+}
+
+bool uart_terminal_scene_help_on_event(void* context, SceneManagerEvent event) {
+    UART_TerminalApp* app = context;
+    bool consumed = false;
+    UNUSED(app);
+    UNUSED(event);
+    return consumed;
+}
+
+void uart_terminal_scene_help_on_exit(void* context) {
+    UART_TerminalApp* app = context;
+    // Clear views
+    widget_reset(app->widget);
+}

+ 47 - 0
scenes/uart_terminal_scene_hex_input.c

@@ -0,0 +1,47 @@
+#include "../uart_terminal_app_i.h"
+
+void uart_terminal_scene_hex_input_callback(void* context) {
+    UART_TerminalApp* app = context;
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventStartConsole);
+}
+
+void uart_terminal_scene_hex_input_on_enter(void* context) {
+    UART_TerminalApp* app = context;
+
+    // Setup view
+    UART_TextInput* text_input = app->hex_input;
+    // Add help message to header
+    uart_hex_input_set_header_text(text_input, "Send HEX packet to UART");
+    uart_hex_input_set_result_callback(
+        text_input,
+        uart_terminal_scene_hex_input_callback,
+        app,
+        app->text_input_store,
+        UART_TERMINAL_TEXT_INPUT_STORE_SIZE,
+        false);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, UART_TerminalAppViewHexInput);
+}
+
+bool uart_terminal_scene_hex_input_on_event(void* context, SceneManagerEvent event) {
+    UART_TerminalApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == UART_TerminalEventStartConsole) {
+            // Point to custom string to send
+            app->selected_tx_string = app->text_input_store;
+            scene_manager_next_scene(app->scene_manager, UART_TerminalAppViewConsoleOutput);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void uart_terminal_scene_hex_input_on_exit(void* context) {
+    UART_TerminalApp* app = context;
+
+    uart_hex_input_reset(app->text_input);
+}

+ 110 - 0
scenes/uart_terminal_scene_setup.c

@@ -0,0 +1,110 @@
+#include "../uart_terminal_app_i.h"
+
+#define MAX_OPTIONS		25
+
+typedef struct {
+    const char* item_string;
+    int num_options_menu;
+    const char* options_menu[MAX_OPTIONS];
+} UART_Terminal_Setup_Item;
+
+// SETUP_MENU_ITEMS defined in uart_terminal_app_i.h - if you add an entry here, increment it!
+static const UART_Terminal_Setup_Item items[SETUP_MENU_ITEMS] = {
+    {"UART Pins", 2, {"13,14", "15,16"}},
+    {"Baudrate", 25, {"75", "110", "150", "300", "600", "1200", "1800", "2400", "4800", "7200", "9600", "14400", "19200", "31250", "38400", "56000", "57600", "76800", "115200", "128000", "230400", "250000", "256000", "460800", "921600"}},
+    {"HEX mode", 2, {"OFF", "ON"}},
+};
+
+static void uart_terminal_scene_setup_var_list_enter_callback(void* context, uint32_t index) {
+    furi_assert(context);
+    UART_TerminalApp* app = context;
+
+    furi_assert(index < SETUP_MENU_ITEMS);
+    const UART_Terminal_Setup_Item* item = &items[index];
+
+    const int selected_option_index = app->setup_selected_option_index[index];
+    furi_assert(selected_option_index < item->num_options_menu);
+    app->setup_selected_menu_index = index;
+}
+
+static void uart_terminal_scene_setup_var_list_change_callback(VariableItem* item) {
+    furi_assert(item);
+
+    UART_TerminalApp* app = variable_item_get_context(item);
+    furi_assert(app);
+
+    const UART_Terminal_Setup_Item* menu_item = &items[app->setup_selected_menu_index];
+    uint8_t item_index = variable_item_get_current_value_index(item);
+    furi_assert(item_index < menu_item->num_options_menu);
+    variable_item_set_current_value_text(item, menu_item->options_menu[item_index]);
+    app->setup_selected_option_index[app->setup_selected_menu_index] = item_index;
+
+    // UART Pins
+    if(app->setup_selected_menu_index == UART_PINS_ITEM_IDX) {
+        app->new_uart_ch = item_index > 0;
+    }
+
+    // BaudRate
+    if(app->setup_selected_menu_index == BAUDRATE_ITEM_IDX) {
+        app->NEW_BAUDRATE = atoi(menu_item->options_menu[item_index]);
+    }
+
+    // HEX mode
+    if(app->setup_selected_menu_index == HEX_MODE_ITEM_IDX) {
+        bool new_mode = item_index > 0;
+        if(app->hex_mode != new_mode) {
+            app->hex_mode = new_mode;
+            app->text_input_store[0] = '\0';
+            app->selected_menu_index = 0;
+            for(int i = 0; i < START_MENU_ITEMS; ++i) {
+                app->selected_option_index[i] = 0;
+            }
+        }
+    }
+}
+
+void uart_terminal_scene_setup_on_enter(void* context) {
+    UART_TerminalApp* app = context;
+    VariableItemList* var_item_list = app->setup_var_item_list;
+
+    variable_item_list_set_enter_callback(
+        var_item_list, uart_terminal_scene_setup_var_list_enter_callback, app);
+
+    VariableItem* item;
+    for(int i = 0; i < SETUP_MENU_ITEMS; ++i) {
+        item = variable_item_list_add(
+            var_item_list,
+            items[i].item_string,
+            items[i].num_options_menu,
+            uart_terminal_scene_setup_var_list_change_callback,
+            app);
+        variable_item_set_current_value_index(item, app->setup_selected_option_index[i]);
+        variable_item_set_current_value_text(
+            item, items[i].options_menu[app->setup_selected_option_index[i]]);
+    }
+
+    variable_item_list_set_selected_item(
+        var_item_list, scene_manager_get_scene_state(app->scene_manager, UART_TerminalSceneSetup));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, UART_TerminalAppViewSetup);
+}
+
+bool uart_terminal_scene_setup_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UART_TerminalApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeTick) {
+        app->setup_selected_menu_index = variable_item_list_get_selected_item_index(app->setup_var_item_list);
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void uart_terminal_scene_setup_on_exit(void* context) {
+    UART_TerminalApp* app = context;
+    variable_item_list_reset(app->setup_var_item_list);
+}

+ 104 - 61
scenes/uart_terminal_scene_start.c

@@ -1,71 +1,83 @@
 #include "../uart_terminal_app_i.h"
 
-// For each command, define whether additional arguments are needed
-// (enabling text input to fill them out), and whether the console
-// text box should focus at the start of the output or the end
-typedef enum { NO_ARGS = 0, INPUT_ARGS, TOGGLE_ARGS } InputArgs;
+// Command action type
+typedef enum { NO_ACTION = 0, OPEN_SETUP, OPEN_PORT, SEND_CMD, SEND_AT_CMD, SEND_FAST_CMD, OPEN_HELP } ActionType;
+// Command availability in different modes
+typedef enum { OFF = 0, TEXT_MODE = 1, HEX_MODE = 2, BOTH_MODES = 3 } ModeMask;
 
-typedef enum { FOCUS_CONSOLE_END = 0, FOCUS_CONSOLE_START, FOCUS_CONSOLE_TOGGLE } FocusConsole;
+#define MAX_OPTIONS (8)
 
-#define SHOW_STOPSCAN_TIP (true)
-#define NO_TIP (false)
-
-#define MAX_OPTIONS (25)
 typedef struct {
     const char* item_string;
     const char* options_menu[MAX_OPTIONS];
     int num_options_menu;
-    const char* actual_commands[MAX_OPTIONS];
-    InputArgs needs_keyboard;
-    FocusConsole focus_console;
-    bool show_stopscan_tip;
+    ActionType action;
+    ModeMask mode_mask;
 } UART_TerminalItem;
 
+static const char at_str[] = "AT";
+
 // NUM_MENU_ITEMS defined in uart_terminal_app_i.h - if you add an entry here, increment it!
-const UART_TerminalItem items[NUM_MENU_ITEMS] = {
-    {"Console",
-     {"115200", "75", "110", "150", "300", "600", "1200", "1800", "2400", "4800", "7200", "9600", "14400", "19200", "31250", "38400", "56000", "57600", "76800", "128000", "230400", "250000", "256000", "460800", "921600"},
-     25,
-     {"115200", "75", "110", "150", "300", "600", "1200", "1800", "2400", "4800", "7200", "9600", "14400", "19200", "31250", "38400", "56000", "57600", "76800", "128000", "230400", "250000", "256000", "460800", "921600"},
-     NO_ARGS,
-     FOCUS_CONSOLE_END,
-     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,
+static const UART_TerminalItem items[START_MENU_ITEMS] = {
+    {"Setup", {""}, 1, OPEN_SETUP, BOTH_MODES},
+    {"Open port", {""}, 1, OPEN_PORT, BOTH_MODES},
+    {"Send packet", {""}, 1, SEND_CMD, HEX_MODE},
+    {"Send command", {""}, 1, SEND_CMD, TEXT_MODE},
+    {"Send AT command", {""}, 1, SEND_AT_CMD, TEXT_MODE},
+    {"Fast cmd", 
      {"help", "uptime", "date", "df -h", "ps", "dmesg", "reboot", "poweroff"},
-     INPUT_ARGS,
-     FOCUS_CONSOLE_END,
-     NO_TIP},
-    {"Help", {""}, 1, {"help"}, NO_ARGS, FOCUS_CONSOLE_START, SHOW_STOPSCAN_TIP},
+     8, SEND_FAST_CMD, TEXT_MODE},
+    {"Help", {""}, 1, OPEN_HELP, BOTH_MODES},
 };
 
+static uint8_t menu_items_num = 0;
+static uint8_t item_indexes[START_MENU_ITEMS] = { 0 };
+
 static void uart_terminal_scene_start_var_list_enter_callback(void* context, uint32_t index) {
     furi_assert(context);
     UART_TerminalApp* app = context;
 
-    furi_assert(index < NUM_MENU_ITEMS);
-    const UART_TerminalItem* item = &items[index];
+    furi_assert(index < menu_items_num);
+    uint8_t item_index = item_indexes[index];
+    furi_assert(item_index < START_MENU_ITEMS);
+    const UART_TerminalItem* item = &items[item_index];
 
     const int selected_option_index = app->selected_option_index[index];
     furi_assert(selected_option_index < item->num_options_menu);
-    app->selected_tx_string = item->actual_commands[selected_option_index];
-    app->is_command = (1 <= index);
+    app->selected_tx_string = item->options_menu[selected_option_index];
+    app->is_command = false;
     app->is_custom_tx_string = false;
     app->selected_menu_index = index;
-    app->focus_console_start = (item->focus_console == FOCUS_CONSOLE_TOGGLE) ?
-                                   (selected_option_index == 0) :
-                                   item->focus_console;
-    app->show_stopscan_tip = item->show_stopscan_tip;
-
-    bool needs_keyboard = (item->needs_keyboard == TOGGLE_ARGS) ? (selected_option_index != 0) :
-                                                                  item->needs_keyboard;
-    if(needs_keyboard) {
-        view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventStartKeyboard);
-    } else {
+
+    switch (item->action)
+    {
+    case OPEN_SETUP:
+        view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventSetup);
+        return;
+    case SEND_AT_CMD:
+    case SEND_CMD:
+    case SEND_FAST_CMD:
+        app->is_command = true;
+
+        if(item->action == SEND_AT_CMD) {
+            app->selected_tx_string = at_str;
+        }
+
+        if(app->hex_mode) {
+            view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventStartKeyboardHex);
+        }
+        else {
+            view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventStartKeyboardText);
+        }
+        return;
+    case OPEN_PORT:
         view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventStartConsole);
+        return;
+    case OPEN_HELP:
+        view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventStartHelp);
+        return;
+    default:
+        return;
     }
 }
 
@@ -75,37 +87,56 @@ static void uart_terminal_scene_start_var_list_change_callback(VariableItem* ite
     UART_TerminalApp* app = variable_item_get_context(item);
     furi_assert(app);
 
-    const UART_TerminalItem* menu_item = &items[app->selected_menu_index];
-    uint8_t item_index = variable_item_get_current_value_index(item);
-    furi_assert(item_index < menu_item->num_options_menu);
-    variable_item_set_current_value_text(item, menu_item->options_menu[item_index]);
-    app->selected_option_index[app->selected_menu_index] = item_index;
+    uint8_t item_index = item_indexes[app->selected_menu_index];
+    const UART_TerminalItem* menu_item = &items[item_index];
+    uint8_t option_index = variable_item_get_current_value_index(item);
+    furi_assert(option_index < menu_item->num_options_menu);
+    variable_item_set_current_value_text(item, menu_item->options_menu[option_index]);
+    app->selected_option_index[app->selected_menu_index] = option_index;
 }
 
 void uart_terminal_scene_start_on_enter(void* context) {
     UART_TerminalApp* app = context;
     VariableItemList* var_item_list = app->var_item_list;
 
+    for(int i = 0; i < START_MENU_ITEMS; ++i) {
+        app->selected_option_index[i] = 0;
+    }
+
     variable_item_list_set_enter_callback(
         var_item_list, uart_terminal_scene_start_var_list_enter_callback, app);
 
     VariableItem* item;
-    for(int i = 0; i < NUM_MENU_ITEMS; ++i) {
-        item = variable_item_list_add(
-            var_item_list,
-            items[i].item_string,
-            items[i].num_options_menu,
-            uart_terminal_scene_start_var_list_change_callback,
-            app);
-        variable_item_set_current_value_index(item, app->selected_option_index[i]);
-        variable_item_set_current_value_text(
-            item, items[i].options_menu[app->selected_option_index[i]]);
+    menu_items_num = 0;
+    for(int i = 0; i < START_MENU_ITEMS; ++i) {
+        bool enabled = false;
+        if(app->hex_mode && (items[i].mode_mask & HEX_MODE)) {
+            enabled = true;
+        }
+        if(!app->hex_mode && (items[i].mode_mask & TEXT_MODE)) {
+            enabled = true;
+        }
+
+        if(enabled) {
+            item = variable_item_list_add(
+                var_item_list,
+                items[i].item_string,
+                items[i].num_options_menu,
+                uart_terminal_scene_start_var_list_change_callback,
+                app);
+            variable_item_set_current_value_index(item, app->selected_option_index[i]);
+            variable_item_set_current_value_text(
+                item, items[i].options_menu[app->selected_option_index[i]]);
+
+            item_indexes[menu_items_num] = i;
+            menu_items_num++;
+        }
     }
 
     variable_item_list_set_selected_item(
         var_item_list, scene_manager_get_scene_state(app->scene_manager, UART_TerminalSceneStart));
 
-    view_dispatcher_switch_to_view(app->view_dispatcher, UART_TerminalAppViewVarItemList);
+	view_dispatcher_switch_to_view(app->view_dispatcher, UART_TerminalAppViewVarItemList);
 }
 
 bool uart_terminal_scene_start_on_event(void* context, SceneManagerEvent event) {
@@ -114,14 +145,26 @@ bool uart_terminal_scene_start_on_event(void* context, SceneManagerEvent event)
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == UART_TerminalEventStartKeyboard) {
+        if(event.event == UART_TerminalEventSetup) {
+            scene_manager_set_scene_state(
+                app->scene_manager, UART_TerminalSceneStart, app->selected_menu_index);
+            scene_manager_next_scene(app->scene_manager, UART_TerminalAppViewSetup);
+        } else if(event.event == UART_TerminalEventStartKeyboardText) {
             scene_manager_set_scene_state(
                 app->scene_manager, UART_TerminalSceneStart, app->selected_menu_index);
             scene_manager_next_scene(app->scene_manager, UART_TerminalAppViewTextInput);
+        } else if(event.event == UART_TerminalEventStartKeyboardHex) {
+            scene_manager_set_scene_state(
+                app->scene_manager, UART_TerminalSceneStart, app->selected_menu_index);
+            scene_manager_next_scene(app->scene_manager, UART_TerminalAppViewHexInput);
         } else if(event.event == UART_TerminalEventStartConsole) {
             scene_manager_set_scene_state(
                 app->scene_manager, UART_TerminalSceneStart, app->selected_menu_index);
             scene_manager_next_scene(app->scene_manager, UART_TerminalAppViewConsoleOutput);
+        } else if(event.event == UART_TerminalEventStartHelp) {
+            scene_manager_set_scene_state(
+                app->scene_manager, UART_TerminalSceneStart, app->selected_menu_index);
+            scene_manager_next_scene(app->scene_manager, UART_TerminalAppViewHelp);
         }
         consumed = true;
     } else if(event.type == SceneManagerEventTypeTick) {

+ 518 - 0
uart_hex_input.c

@@ -0,0 +1,518 @@
+#include "uart_hex_input.h"
+#include <gui/elements.h>
+#include "uart_terminal_icons.h"
+#include "uart_terminal_app_i.h"
+#include <furi.h>
+
+struct UART_TextInput {
+    View* view;
+    FuriTimer* timer;
+};
+
+typedef struct {
+    const char text;
+    const uint8_t x;
+    const uint8_t y;
+} UART_TextInputKey;
+
+typedef struct {
+    const char* header;
+    char* text_buffer;
+    size_t text_buffer_size;
+    bool clear_default_text;
+
+    UART_TextInputCallback callback;
+    void* callback_context;
+
+    uint8_t selected_row;
+    uint8_t selected_column;
+
+    UART_TextInputValidatorCallback validator_callback;
+    void* validator_callback_context;
+    FuriString* validator_text;
+    bool valadator_message_visible;
+} UART_TextInputModel;
+
+static const uint8_t keyboard_origin_x = 5;
+static const uint8_t keyboard_origin_y = 28;
+static const uint8_t keyboard_row_count = 2;
+
+#define ENTER_KEY '\r'
+#define BACKSPACE_KEY '\b'
+
+static const UART_TextInputKey keyboard_keys_row_1[] = {
+    {'0', 0, 12},
+    {'1', 11, 12},
+    {'2', 22, 12},
+    {'3', 33, 12},
+    {'4', 44, 12},
+    {'5', 55, 12},
+    {'6', 66, 12},
+    {'7', 77, 12},
+    {BACKSPACE_KEY, 103, 4},
+};
+
+static const UART_TextInputKey keyboard_keys_row_2[] = {
+    {'8', 0, 26},
+    {'9', 11, 26},
+    {'A', 22, 26},
+    {'B', 33, 26},
+    {'C', 44, 26},
+    {'D', 55, 26},
+    {'E', 66, 26},
+    {'F', 77, 26},
+    {ENTER_KEY, 95, 17},
+};
+
+static uint8_t get_row_size(uint8_t row_index) {
+    uint8_t row_size = 0;
+
+    switch(row_index + 1) {
+    case 1:
+        row_size = sizeof(keyboard_keys_row_1) / sizeof(UART_TextInputKey);
+        break;
+    case 2:
+        row_size = sizeof(keyboard_keys_row_2) / sizeof(UART_TextInputKey);
+        break;
+    }
+
+    return row_size;
+}
+
+static const UART_TextInputKey* get_row(uint8_t row_index) {
+    const UART_TextInputKey* row = NULL;
+
+    switch(row_index + 1) {
+    case 1:
+        row = keyboard_keys_row_1;
+        break;
+    case 2:
+        row = keyboard_keys_row_2;
+        break;
+    }
+
+    return row;
+}
+
+static char get_selected_char(UART_TextInputModel* model) {
+    return get_row(model->selected_row)[model->selected_column].text;
+}
+
+static void uart_hex_input_backspace_cb(UART_TextInputModel* model) {
+    uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer);
+    if(text_length > 0) {
+        model->text_buffer[text_length - 1] = 0;
+    }
+}
+
+static void uart_hex_input_view_draw_callback(Canvas* canvas, void* _model) {
+    UART_TextInputModel* model = _model;
+    uint8_t needed_string_width = canvas_width(canvas) - 8;
+    uint8_t start_pos = 4;
+
+    const char* text = model->text_buffer;
+
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    canvas_draw_str(canvas, 2, 7, model->header);
+    elements_slightly_rounded_frame(canvas, 1, 8, 126, 12);
+
+    if(canvas_string_width(canvas, text) > needed_string_width) {
+        canvas_draw_str(canvas, start_pos, 17, "...");
+        start_pos += 6;
+        needed_string_width -= 8;
+    }
+
+    while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) {
+        text++;
+    }
+
+    if(model->clear_default_text) {
+        elements_slightly_rounded_box(
+            canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10);
+        canvas_set_color(canvas, ColorWhite);
+    } else {
+        canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 18, "|");
+        canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 18, "|");
+    }
+    canvas_draw_str(canvas, start_pos, 17, text);
+
+    canvas_set_font(canvas, FontKeyboard);
+
+    for(uint8_t row = 0; row <= keyboard_row_count; row++) {
+        const uint8_t column_count = get_row_size(row);
+        const UART_TextInputKey* keys = get_row(row);
+
+        for(size_t column = 0; column < column_count; column++) {
+            if(keys[column].text == ENTER_KEY) {
+                canvas_set_color(canvas, ColorBlack);
+                if(model->selected_row == row && model->selected_column == column) {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeySaveSelected_24x11);
+                } else {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeySave_24x11);
+                }
+            } else if(keys[column].text == BACKSPACE_KEY) {
+                canvas_set_color(canvas, ColorBlack);
+                if(model->selected_row == row && model->selected_column == column) {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeyBackspaceSelected_16x9);
+                } else {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeyBackspace_16x9);
+                }
+            } else {
+                if(model->selected_row == row && model->selected_column == column) {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_box(
+                        canvas,
+                        keyboard_origin_x + keys[column].x - 1,
+                        keyboard_origin_y + keys[column].y - 8,
+                        7,
+                        10);
+                    canvas_set_color(canvas, ColorWhite);
+                } else {
+                    canvas_set_color(canvas, ColorBlack);
+                }
+
+                canvas_draw_glyph(
+                    canvas,
+                    keyboard_origin_x + keys[column].x,
+                    keyboard_origin_y + keys[column].y,
+                    keys[column].text);
+            }
+        }
+    }
+    if(model->valadator_message_visible) {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_box(canvas, 8, 10, 110, 48);
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42);
+        canvas_draw_rframe(canvas, 8, 8, 112, 50, 3);
+        canvas_draw_rframe(canvas, 9, 9, 110, 48, 2);
+        elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text));
+        canvas_set_font(canvas, FontKeyboard);
+    }
+}
+
+static void
+    uart_hex_input_handle_up(UART_TextInput* uart_text_input, UART_TextInputModel* model) {
+    UNUSED(uart_text_input);
+    if(model->selected_row > 0) {
+        model->selected_row--;
+        if(model->selected_column > get_row_size(model->selected_row) - 6) {
+            model->selected_column = model->selected_column + 1;
+        }
+    }
+}
+
+static void
+    uart_hex_input_handle_down(UART_TextInput* uart_text_input, UART_TextInputModel* model) {
+    UNUSED(uart_text_input);
+    if(model->selected_row < keyboard_row_count - 1) {
+        model->selected_row++;
+        if(model->selected_column > get_row_size(model->selected_row) - 4) {
+            model->selected_column = model->selected_column - 1;
+        }
+    }
+}
+
+static void
+    uart_hex_input_handle_left(UART_TextInput* uart_text_input, UART_TextInputModel* model) {
+    UNUSED(uart_text_input);
+    if(model->selected_column > 0) {
+        model->selected_column--;
+    } else {
+        model->selected_column = get_row_size(model->selected_row) - 1;
+    }
+}
+
+static void
+    uart_hex_input_handle_right(UART_TextInput* uart_text_input, UART_TextInputModel* model) {
+    UNUSED(uart_text_input);
+    if(model->selected_column < get_row_size(model->selected_row) - 1) {
+        model->selected_column++;
+    } else {
+        model->selected_column = 0;
+    }
+}
+
+static void uart_hex_input_handle_ok(
+    UART_TextInput* uart_text_input,
+    UART_TextInputModel* model,
+    bool shift) {
+    UNUSED(shift);
+    char selected = get_selected_char(model);
+    uint8_t text_length = strlen(model->text_buffer);
+
+    if(selected == ENTER_KEY) {
+        if(model->validator_callback &&
+           (!model->validator_callback(
+               model->text_buffer, model->validator_text, model->validator_callback_context))) {
+            model->valadator_message_visible = true;
+            furi_timer_start(uart_text_input->timer, furi_kernel_get_tick_frequency() * 4);
+        } else if(model->callback != 0 && text_length > 0) {
+            model->callback(model->callback_context);
+        }
+    } else if(selected == BACKSPACE_KEY) {
+        uart_hex_input_backspace_cb(model);
+    } else {
+        if(model->clear_default_text) {
+            text_length = 0;
+        }
+        if(text_length < (model->text_buffer_size - 1)) {
+            model->text_buffer[text_length] = selected;
+            model->text_buffer[text_length + 1] = 0;
+        }
+    }
+    model->clear_default_text = false;
+}
+
+static bool uart_hex_input_view_input_callback(InputEvent* event, void* context) {
+    UART_TextInput* uart_text_input = context;
+    furi_assert(uart_text_input);
+
+    bool consumed = false;
+
+    // Acquire model
+    UART_TextInputModel* model = view_get_model(uart_text_input->view);
+
+    if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) &&
+       model->valadator_message_visible) {
+        model->valadator_message_visible = false;
+        consumed = true;
+    } else if(event->type == InputTypeShort) {
+        consumed = true;
+        switch(event->key) {
+        case InputKeyUp:
+            uart_hex_input_handle_up(uart_text_input, model);
+            break;
+        case InputKeyDown:
+            uart_hex_input_handle_down(uart_text_input, model);
+            break;
+        case InputKeyLeft:
+            uart_hex_input_handle_left(uart_text_input, model);
+            break;
+        case InputKeyRight:
+            uart_hex_input_handle_right(uart_text_input, model);
+            break;
+        case InputKeyOk:
+            uart_hex_input_handle_ok(uart_text_input, model, false);
+            break;
+        default:
+            consumed = false;
+            break;
+        }
+    } else if(event->type == InputTypeLong) {
+        consumed = true;
+        switch(event->key) {
+        case InputKeyUp:
+            uart_hex_input_handle_up(uart_text_input, model);
+            break;
+        case InputKeyDown:
+            uart_hex_input_handle_down(uart_text_input, model);
+            break;
+        case InputKeyLeft:
+            uart_hex_input_handle_left(uart_text_input, model);
+            break;
+        case InputKeyRight:
+            uart_hex_input_handle_right(uart_text_input, model);
+            break;
+        case InputKeyOk:
+            uart_hex_input_handle_ok(uart_text_input, model, true);
+            break;
+        case InputKeyBack:
+            uart_hex_input_backspace_cb(model);
+            break;
+        default:
+            consumed = false;
+            break;
+        }
+    } else if(event->type == InputTypeRepeat) {
+        consumed = true;
+        switch(event->key) {
+        case InputKeyUp:
+            uart_hex_input_handle_up(uart_text_input, model);
+            break;
+        case InputKeyDown:
+            uart_hex_input_handle_down(uart_text_input, model);
+            break;
+        case InputKeyLeft:
+            uart_hex_input_handle_left(uart_text_input, model);
+            break;
+        case InputKeyRight:
+            uart_hex_input_handle_right(uart_text_input, model);
+            break;
+        case InputKeyBack:
+            uart_hex_input_backspace_cb(model);
+            break;
+        default:
+            consumed = false;
+            break;
+        }
+    }
+
+    // Commit model
+    view_commit_model(uart_text_input->view, consumed);
+
+    return consumed;
+}
+
+void uart_hex_input_timer_callback(void* context) {
+    furi_assert(context);
+    UART_TextInput* uart_text_input = context;
+
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { model->valadator_message_visible = false; },
+        true);
+}
+
+UART_TextInput* uart_hex_input_alloc() {
+    UART_TextInput* uart_text_input = malloc(sizeof(UART_TextInput));
+    uart_text_input->view = view_alloc();
+    view_set_context(uart_text_input->view, uart_text_input);
+    view_allocate_model(uart_text_input->view, ViewModelTypeLocking, sizeof(UART_TextInputModel));
+    view_set_draw_callback(uart_text_input->view, uart_hex_input_view_draw_callback);
+    view_set_input_callback(uart_text_input->view, uart_hex_input_view_input_callback);
+
+    uart_text_input->timer =
+        furi_timer_alloc(uart_hex_input_timer_callback, FuriTimerTypeOnce, uart_text_input);
+
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { model->validator_text = furi_string_alloc(); },
+        false);
+
+    uart_text_input_reset(uart_text_input);
+
+    return uart_text_input;
+}
+
+void uart_hex_input_free(UART_TextInput* uart_text_input) {
+    furi_assert(uart_text_input);
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { furi_string_free(model->validator_text); },
+        false);
+
+    // Send stop command
+    furi_timer_stop(uart_text_input->timer);
+    // Release allocated memory
+    furi_timer_free(uart_text_input->timer);
+
+    view_free(uart_text_input->view);
+
+    free(uart_text_input);
+}
+
+void uart_hex_input_reset(UART_TextInput* uart_text_input) {
+    furi_assert(uart_text_input);
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        {
+            model->text_buffer_size = 0;
+            model->header = "";
+            model->selected_row = 0;
+            model->selected_column = 0;
+            model->clear_default_text = false;
+            model->text_buffer = NULL;
+            model->text_buffer_size = 0;
+            model->callback = NULL;
+            model->callback_context = NULL;
+            model->validator_callback = NULL;
+            model->validator_callback_context = NULL;
+            furi_string_reset(model->validator_text);
+            model->valadator_message_visible = false;
+        },
+        true);
+}
+
+View* uart_hex_input_get_view(UART_TextInput* uart_text_input) {
+    furi_assert(uart_text_input);
+    return uart_text_input->view;
+}
+
+void uart_hex_input_set_result_callback(
+    UART_TextInput* uart_text_input,
+    UART_TextInputCallback callback,
+    void* callback_context,
+    char* text_buffer,
+    size_t text_buffer_size,
+    bool clear_default_text) {
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        {
+            model->callback = callback;
+            model->callback_context = callback_context;
+            model->text_buffer = text_buffer;
+            model->text_buffer_size = text_buffer_size;
+            model->clear_default_text = clear_default_text;
+            if(text_buffer && text_buffer[0] != '\0') {
+                // Set focus on Save
+                model->selected_row = 1;
+                model->selected_column = 8;
+            }
+        },
+        true);
+}
+
+void uart_hex_input_set_validator(
+    UART_TextInput* uart_text_input,
+    UART_TextInputValidatorCallback callback,
+    void* callback_context) {
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        {
+            model->validator_callback = callback;
+            model->validator_callback_context = callback_context;
+        },
+        true);
+}
+
+UART_TextInputValidatorCallback
+    uart_hex_input_get_validator_callback(UART_TextInput* uart_text_input) {
+    UART_TextInputValidatorCallback validator_callback = NULL;
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { validator_callback = model->validator_callback; },
+        false);
+    return validator_callback;
+}
+
+void* uart_hex_input_get_validator_callback_context(UART_TextInput* uart_text_input) {
+    void* validator_callback_context = NULL;
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { validator_callback_context = model->validator_callback_context; },
+        false);
+    return validator_callback_context;
+}
+
+void uart_hex_input_set_header_text(UART_TextInput* uart_text_input, const char* text) {
+    with_view_model(
+        uart_text_input->view, UART_TextInputModel * model, { model->header = text; }, true);
+}

+ 82 - 0
uart_hex_input.h

@@ -0,0 +1,82 @@
+#pragma once
+
+#include <gui/view.h>
+#include "uart_validators.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Text input anonymous structure */
+typedef struct UART_TextInput UART_TextInput;
+typedef void (*UART_TextInputCallback)(void* context);
+typedef bool (*UART_TextInputValidatorCallback)(const char* text, FuriString* error, void* context);
+
+/** Allocate and initialize text input 
+ * 
+ * This text input is used to enter string
+ *
+ * @return     UART_TextInput instance
+ */
+UART_TextInput* uart_hex_input_alloc();
+
+/** Deinitialize and free text input
+ *
+ * @param      uart_text_input  UART_TextInput instance
+ */
+void uart_hex_input_free(UART_TextInput* uart_text_input);
+
+/** Clean text input view Note: this function does not free memory
+ *
+ * @param      uart_text_input  Text input instance
+ */
+void uart_hex_input_reset(UART_TextInput* uart_text_input);
+
+/** Get text input view
+ *
+ * @param      uart_text_input  UART_TextInput instance
+ *
+ * @return     View instance that can be used for embedding
+ */
+View* uart_hex_input_get_view(UART_TextInput* uart_text_input);
+
+/** Set text input result callback
+ *
+ * @param      uart_text_input          UART_TextInput instance
+ * @param      callback            callback fn
+ * @param      callback_context    callback context
+ * @param      text_buffer         pointer to YOUR text buffer, that we going
+ *                                 to modify
+ * @param      text_buffer_size    YOUR text buffer size in bytes. Max string
+ *                                 length will be text_buffer_size-1.
+ * @param      clear_default_text  clear text from text_buffer on first OK
+ *                                 event
+ */
+void uart_hex_input_set_result_callback(
+    UART_TextInput* uart_text_input,
+    UART_TextInputCallback callback,
+    void* callback_context,
+    char* text_buffer,
+    size_t text_buffer_size,
+    bool clear_default_text);
+
+void uart_hex_input_set_validator(
+    UART_TextInput* uart_text_input,
+    UART_TextInputValidatorCallback callback,
+    void* callback_context);
+
+UART_TextInputValidatorCallback
+    uart_hex_input_get_validator_callback(UART_TextInput* uart_text_input);
+
+void* uart_hex_input_get_validator_callback_context(UART_TextInput* uart_text_input);
+
+/** Set text input header text
+ *
+ * @param      uart_text_input  UART_TextInput instance
+ * @param      text        text to be shown
+ */
+void uart_hex_input_set_header_text(UART_TextInput* uart_text_input, const char* text);
+
+#ifdef __cplusplus
+}
+#endif

+ 26 - 1
uart_terminal_app.c

@@ -46,10 +46,23 @@ UART_TerminalApp* uart_terminal_app_alloc() {
         UART_TerminalAppViewVarItemList,
         variable_item_list_get_view(app->var_item_list));
 
-    for(int i = 0; i < NUM_MENU_ITEMS; ++i) {
+    for(int i = 0; i < START_MENU_ITEMS; ++i) {
         app->selected_option_index[i] = 0;
     }
 
+    app->setup_var_item_list = variable_item_list_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        UART_TerminalAppViewSetup,
+        variable_item_list_get_view(app->setup_var_item_list));
+
+    for(int i = 0; i < SETUP_MENU_ITEMS; ++i) {
+        app->setup_selected_option_index[i] = 0;
+    }
+
+    app->widget = widget_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, UART_TerminalAppViewHelp, widget_get_view(app->widget));
+
     app->text_box = text_box_alloc();
     view_dispatcher_add_view(
         app->view_dispatcher, UART_TerminalAppViewConsoleOutput, text_box_get_view(app->text_box));
@@ -62,6 +75,15 @@ UART_TerminalApp* uart_terminal_app_alloc() {
         UART_TerminalAppViewTextInput,
         uart_text_input_get_view(app->text_input));
 
+    app->hex_input = uart_hex_input_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        UART_TerminalAppViewHexInput,
+        uart_text_input_get_view(app->hex_input)
+        );
+
+    app->setup_selected_option_index[BAUDRATE_ITEM_IDX] = DEFAULT_BAUDRATE_OPT_IDX;
+
     scene_manager_next_scene(app->scene_manager, UART_TerminalSceneStart);
 
     return app;
@@ -72,8 +94,11 @@ void uart_terminal_app_free(UART_TerminalApp* app) {
 
     // Views
     view_dispatcher_remove_view(app->view_dispatcher, UART_TerminalAppViewVarItemList);
+    view_dispatcher_remove_view(app->view_dispatcher, UART_TerminalAppViewSetup);
     view_dispatcher_remove_view(app->view_dispatcher, UART_TerminalAppViewConsoleOutput);
     view_dispatcher_remove_view(app->view_dispatcher, UART_TerminalAppViewTextInput);
+    view_dispatcher_remove_view(app->view_dispatcher, UART_TerminalAppViewHexInput);
+    
     text_box_free(app->text_box);
     furi_string_free(app->text_box_store);
     uart_text_input_free(app->text_input);

+ 19 - 7
uart_terminal_app_i.h

@@ -9,14 +9,16 @@
 #include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>
 #include <gui/modules/text_box.h>
+#include <gui/modules/widget.h>
 #include <gui/modules/variable_item_list.h>
 #include "uart_text_input.h"
+#include "uart_hex_input.h"
 
-#define NUM_MENU_ITEMS (5)
+#define START_MENU_ITEMS (7)
+#define SETUP_MENU_ITEMS (3)
 
 #define UART_TERMINAL_TEXT_BOX_STORE_SIZE (4096)
 #define UART_TERMINAL_TEXT_INPUT_STORE_SIZE (512)
-#define UART_CH (FuriHalUartIdUSART1)
 
 struct UART_TerminalApp {
     Gui* gui;
@@ -28,23 +30,33 @@ struct UART_TerminalApp {
     size_t text_box_store_strlen;
     TextBox* text_box;
     UART_TextInput* text_input;
-
+    UART_TextInput* hex_input;
+    Widget* widget;
     VariableItemList* var_item_list;
-
+    VariableItemList* setup_var_item_list;
     UART_TerminalUart* uart;
+
+    int setup_selected_menu_index;
+    int setup_selected_option_index[SETUP_MENU_ITEMS];
     int selected_menu_index;
-    int selected_option_index[NUM_MENU_ITEMS];
+    int selected_option_index[START_MENU_ITEMS];
     const char* selected_tx_string;
+
     bool is_command;
     bool is_custom_tx_string;
-    bool focus_console_start;
-    bool show_stopscan_tip;
+    bool hex_mode;
+    uint8_t uart_ch;
+    uint8_t new_uart_ch;
     int BAUDRATE;
+    int NEW_BAUDRATE;
     int TERMINAL_MODE; //1=AT mode, 0=other mode
 };
 
 typedef enum {
     UART_TerminalAppViewVarItemList,
+    UART_TerminalAppViewSetup,
     UART_TerminalAppViewConsoleOutput,
     UART_TerminalAppViewTextInput,
+    UART_TerminalAppViewHexInput,
+    UART_TerminalAppViewHelp,
 } UART_TerminalAppView;

+ 4 - 1
uart_terminal_custom_event.h

@@ -2,6 +2,9 @@
 
 typedef enum {
     UART_TerminalEventRefreshConsoleOutput = 0,
+    UART_TerminalEventSetup,
     UART_TerminalEventStartConsole,
-    UART_TerminalEventStartKeyboard,
+    UART_TerminalEventStartKeyboardText,
+    UART_TerminalEventStartKeyboardHex,
+    UART_TerminalEventStartHelp,
 } UART_TerminalCustomEvent;

+ 16 - 11
uart_terminal_uart.c

@@ -1,9 +1,6 @@
 #include "uart_terminal_app_i.h"
 #include "uart_terminal_uart.h"
 
-//#define UART_CH (FuriHalUartIdUSART1)
-//#define BAUDRATE (115200)
-
 struct UART_TerminalUart {
     UART_TerminalApp* app;
     FuriThread* rx_thread;
@@ -56,8 +53,8 @@ static int32_t uart_worker(void* context) {
     return 0;
 }
 
-void uart_terminal_uart_tx(uint8_t* data, size_t len) {
-    furi_hal_uart_tx(UART_CH, data, len);
+void uart_terminal_uart_tx(uint8_t uart_ch, uint8_t* data, size_t len) {
+    furi_hal_uart_tx(uart_ch, data, len);
 }
 
 UART_TerminalUart* uart_terminal_uart_init(UART_TerminalApp* app) {
@@ -73,12 +70,18 @@ UART_TerminalUart* uart_terminal_uart_init(UART_TerminalApp* app) {
 
     furi_thread_start(uart->rx_thread);
 
-    furi_hal_console_disable();
     if(app->BAUDRATE == 0) {
         app->BAUDRATE = 115200;
     }
-    furi_hal_uart_set_br(UART_CH, app->BAUDRATE);
-    furi_hal_uart_set_irq_cb(UART_CH, uart_terminal_uart_on_irq_cb, uart);
+
+    if(app->uart_ch == FuriHalUartIdUSART1) {
+        furi_hal_console_disable();
+		furi_hal_uart_set_br(app->uart_ch, app->BAUDRATE);
+    } else if(app->uart_ch == FuriHalUartIdLPUART1) {
+        furi_hal_uart_init(app->uart_ch, app->BAUDRATE);
+    }
+
+    furi_hal_uart_set_irq_cb(app->uart_ch, uart_terminal_uart_on_irq_cb, uart);
 
     return uart;
 }
@@ -90,8 +93,10 @@ void uart_terminal_uart_free(UART_TerminalUart* uart) {
     furi_thread_join(uart->rx_thread);
     furi_thread_free(uart->rx_thread);
 
-    furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL);
-    furi_hal_console_enable();
+    furi_hal_uart_set_irq_cb(uart->app->uart_ch, NULL, NULL);
+
+    if(uart->app->uart_ch == FuriHalUartIdUSART1)
+        furi_hal_console_enable();
 
     free(uart);
-}
+}

+ 1 - 1
uart_terminal_uart.h

@@ -9,6 +9,6 @@ typedef struct UART_TerminalUart UART_TerminalUart;
 void uart_terminal_uart_set_handle_rx_data_cb(
     UART_TerminalUart* uart,
     void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context));
-void uart_terminal_uart_tx(uint8_t* data, size_t len);
+void uart_terminal_uart_tx(uint8_t uart_ch, uint8_t* data, size_t len);
 UART_TerminalUart* uart_terminal_uart_init(UART_TerminalApp* app);
 void uart_terminal_uart_free(UART_TerminalUart* uart);