diff --git a/ReadMe.md b/ReadMe.md index 375ecac8e..c6e29a6be 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -126,6 +126,7 @@ You can support us by using links or addresses below: - **iButton Fuzzer** [(by xMasterX)](https://github.com/xMasterX/ibutton-fuzzer) - HEX Viewer [(by QtRoS)](https://github.com/QtRoS/flipper-zero-hex-viewer) - POCSAG Pager [(by xMasterX & Shmuma)](https://github.com/xMasterX/flipper-pager) +- UART Terminal [(by cool4uma)](https://github.com/cool4uma/UART_Terminal/tree/main) Games: - DOOM (fixed) [(by p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/) diff --git a/applications/debug/uart_echo/application.fam b/applications/debug/uart_echo/application.fam index a16c611bd..9bc61edc8 100644 --- a/applications/debug/uart_echo/application.fam +++ b/applications/debug/uart_echo/application.fam @@ -8,5 +8,5 @@ App( stack_size=2 * 1024, order=70, fap_icon="uart_10px.png", - fap_category="Misc", + fap_category="GPIO", ) diff --git a/applications/plugins/uart_terminal/LICENSE b/applications/plugins/uart_terminal/LICENSE new file mode 100644 index 000000000..c4613e7ea --- /dev/null +++ b/applications/plugins/uart_terminal/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2023 Malik cool4uma + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/applications/plugins/uart_terminal/README.md b/applications/plugins/uart_terminal/README.md new file mode 100644 index 000000000..7e2cfd212 --- /dev/null +++ b/applications/plugins/uart_terminal/README.md @@ -0,0 +1,45 @@ +# UART Terminal for Flipper Zero +[Flipper Zero](https://flipperzero.one/) app to control various devices via UART interface. + +## Capabilities +- Read log and command output by uart +- Send commands by uart +- Set baud rate +- Fast commands + +## Connecting +| Flipper Zero pin | UART interface | +| ---------------- | --------------- | +| 13 TX | RX | +| 14 RX | TX | +|8, 18 GND | GND | + +Info: If possible, do not power your devices from 3V3 (pin 9) Flipper Zero. It does not support hot plugging. + +## Keyboard +UART_terminal uses its own special keyboard for work, which has all the symbols necessary for working in the console. + +To accommodate more characters on a small display, some characters are called up by holding. + +![kbf](https://user-images.githubusercontent.com/122148894/212286637-7063f1ee-c6ff-46b9-8dc5-79a5f367fab1.png) + + +## 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. + +Or use the tool [uFBT](https://github.com/flipperdevices/flipperzero-ufbt) for building applications for Flipper Zero. + +## How it works + + +![1f](https://user-images.githubusercontent.com/122148894/211161450-6d177638-3bfa-42a8-9c73-0cf3af5e5ca7.jpg) + + +![2f](https://user-images.githubusercontent.com/122148894/211161456-4d2be15b-4a05-4450-a62e-edcaab3772fd.jpg) + + +![4f](https://user-images.githubusercontent.com/122148894/211161461-4507120b-42df-441f-9e01-e4517aa83537.jpg) + +## 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. diff --git a/applications/plugins/uart_terminal/application.fam b/applications/plugins/uart_terminal/application.fam new file mode 100644 index 000000000..c6cea5362 --- /dev/null +++ b/applications/plugins/uart_terminal/application.fam @@ -0,0 +1,13 @@ +App( + appid="uart_terminal", + name="UART Terminal", + apptype=FlipperAppType.EXTERNAL, + entry_point="uart_terminal_app", + cdefines=["APP_UART_TERMINAL"], + requires=["gui"], + stack_size=1 * 1024, + order=90, + fap_icon="uart_terminal.png", + fap_category="GPIO", + fap_icon_assets="assets", +) diff --git a/applications/plugins/uart_terminal/assets/KeyBackspaceSelected_16x9.png b/applications/plugins/uart_terminal/assets/KeyBackspaceSelected_16x9.png new file mode 100644 index 000000000..7cc0759a8 Binary files /dev/null and b/applications/plugins/uart_terminal/assets/KeyBackspaceSelected_16x9.png differ diff --git a/applications/plugins/uart_terminal/assets/KeyBackspace_16x9.png b/applications/plugins/uart_terminal/assets/KeyBackspace_16x9.png new file mode 100644 index 000000000..9946232d9 Binary files /dev/null and b/applications/plugins/uart_terminal/assets/KeyBackspace_16x9.png differ diff --git a/applications/plugins/uart_terminal/assets/KeySaveSelected_24x11.png b/applications/plugins/uart_terminal/assets/KeySaveSelected_24x11.png new file mode 100644 index 000000000..eeb3569d3 Binary files /dev/null and b/applications/plugins/uart_terminal/assets/KeySaveSelected_24x11.png differ diff --git a/applications/plugins/uart_terminal/assets/KeySave_24x11.png b/applications/plugins/uart_terminal/assets/KeySave_24x11.png new file mode 100644 index 000000000..e7dba987a Binary files /dev/null and b/applications/plugins/uart_terminal/assets/KeySave_24x11.png differ diff --git a/applications/plugins/uart_terminal/assets/WarningDolphin_45x42.png b/applications/plugins/uart_terminal/assets/WarningDolphin_45x42.png new file mode 100644 index 000000000..d766ffbb4 Binary files /dev/null and b/applications/plugins/uart_terminal/assets/WarningDolphin_45x42.png differ diff --git a/applications/plugins/uart_terminal/scenes/uart_terminal_scene.c b/applications/plugins/uart_terminal/scenes/uart_terminal_scene.c new file mode 100644 index 000000000..451c5d98b --- /dev/null +++ b/applications/plugins/uart_terminal/scenes/uart_terminal_scene.c @@ -0,0 +1,30 @@ +#include "uart_terminal_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const uart_terminal_scene_on_enter_handlers[])(void*) = { +#include "uart_terminal_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 uart_terminal_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "uart_terminal_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 uart_terminal_scene_on_exit_handlers[])(void* context) = { +#include "uart_terminal_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers uart_terminal_scene_handlers = { + .on_enter_handlers = uart_terminal_scene_on_enter_handlers, + .on_event_handlers = uart_terminal_scene_on_event_handlers, + .on_exit_handlers = uart_terminal_scene_on_exit_handlers, + .scene_num = UART_TerminalSceneNum, +}; diff --git a/applications/plugins/uart_terminal/scenes/uart_terminal_scene.h b/applications/plugins/uart_terminal/scenes/uart_terminal_scene.h new file mode 100644 index 000000000..c6f4ed4b4 --- /dev/null +++ b/applications/plugins/uart_terminal/scenes/uart_terminal_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) UART_TerminalScene##id, +typedef enum { +#include "uart_terminal_scene_config.h" + UART_TerminalSceneNum, +} UART_TerminalScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers uart_terminal_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "uart_terminal_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 "uart_terminal_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 "uart_terminal_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/uart_terminal/scenes/uart_terminal_scene_config.h b/applications/plugins/uart_terminal/scenes/uart_terminal_scene_config.h new file mode 100644 index 000000000..febdce167 --- /dev/null +++ b/applications/plugins/uart_terminal/scenes/uart_terminal_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(uart_terminal, start, Start) +ADD_SCENE(uart_terminal, console_output, ConsoleOutput) +ADD_SCENE(uart_terminal, text_input, UART_TextInput) diff --git a/applications/plugins/uart_terminal/scenes/uart_terminal_scene_console_output.c b/applications/plugins/uart_terminal/scenes/uart_terminal_scene_console_output.c new file mode 100644 index 000000000..a9f998124 --- /dev/null +++ b/applications/plugins/uart_terminal/scenes/uart_terminal_scene_console_output.c @@ -0,0 +1,147 @@ +#include "../uart_terminal_app_i.h" + +void uart_terminal_console_output_handle_rx_data_cb(uint8_t* buf, size_t len, void* context) { + furi_assert(context); + UART_TerminalApp* app = context; + + // 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) { + 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); + + view_dispatcher_send_custom_event( + app->view_dispatcher, UART_TerminalEventRefreshConsoleOutput); +} + +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); + } + + //Change baudrate /////////////////////////////////////////////////////////////////////////// + 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("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("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("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("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("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("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); + } + if(0 == strncmp("921600", app->selected_tx_string, strlen("921600")) && + app->BAUDRATE != 921600) { + uart_terminal_uart_free(app->uart); + app->BAUDRATE = 921600; + 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 + text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); + + scene_manager_set_scene_state(app->scene_manager, UART_TerminalSceneConsoleOutput, 0); + view_dispatcher_switch_to_view(app->view_dispatcher, UART_TerminalAppViewConsoleOutput); + + // Register callback to receive data + 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' + 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); + } +} + +bool uart_terminal_scene_console_output_on_event(void* context, SceneManagerEvent event) { + UART_TerminalApp* app = context; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + consumed = true; + } + + return consumed; +} + +void uart_terminal_scene_console_output_on_exit(void* context) { + UART_TerminalApp* app = context; + + // Unregister rx callback + uart_terminal_uart_set_handle_rx_data_cb(app->uart, NULL); + + // Automatically logut when exiting view + //if(app->is_command) { + // uart_terminal_uart_tx((uint8_t*)("exit\n"), strlen("exit\n")); + //} +} diff --git a/applications/plugins/uart_terminal/scenes/uart_terminal_scene_start.c b/applications/plugins/uart_terminal/scenes/uart_terminal_scene_start.c new file mode 100644 index 000000000..db783e9b2 --- /dev/null +++ b/applications/plugins/uart_terminal/scenes/uart_terminal_scene_start.c @@ -0,0 +1,137 @@ +#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; + +typedef enum { FOCUS_CONSOLE_END = 0, FOCUS_CONSOLE_START, FOCUS_CONSOLE_TOGGLE } FocusConsole; + +#define SHOW_STOPSCAN_TIP (true) +#define NO_TIP (false) + +#define MAX_OPTIONS (9) +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; +} UART_TerminalItem; + +// 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", "2400", "9600", "19200", "38400", "57600", "230400", "460800", "921600"}, + 9, + {"115200", "2400", "9600", "19200", "38400", "57600", "230400", "460800", "921600"}, + NO_ARGS, + FOCUS_CONSOLE_TOGGLE, + NO_TIP}, + {"Send command", {""}, 1, {""}, INPUT_ARGS, FOCUS_CONSOLE_END, NO_TIP}, + {"Fast cmd", + {"help", "uptime", "date", "df -h", "ps", "dmesg", "reboot", "poweroff"}, + 8, + {"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}, +}; + +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]; + + 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->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 { + view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventStartConsole); + } +} + +static void uart_terminal_scene_start_var_list_change_callback(VariableItem* item) { + furi_assert(item); + + 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; +} + +void uart_terminal_scene_start_on_enter(void* context) { + UART_TerminalApp* app = context; + VariableItemList* var_item_list = app->var_item_list; + + 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]]); + } + + 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); +} + +bool uart_terminal_scene_start_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UART_TerminalApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == UART_TerminalEventStartKeyboard) { + 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_TerminalEventStartConsole) { + scene_manager_set_scene_state( + app->scene_manager, UART_TerminalSceneStart, app->selected_menu_index); + scene_manager_next_scene(app->scene_manager, UART_TerminalAppViewConsoleOutput); + } + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + app->selected_menu_index = variable_item_list_get_selected_item_index(app->var_item_list); + consumed = true; + } + + return consumed; +} + +void uart_terminal_scene_start_on_exit(void* context) { + UART_TerminalApp* app = context; + variable_item_list_reset(app->var_item_list); +} diff --git a/applications/plugins/uart_terminal/scenes/uart_terminal_scene_text_input.c b/applications/plugins/uart_terminal/scenes/uart_terminal_scene_text_input.c new file mode 100644 index 000000000..62c0792d4 --- /dev/null +++ b/applications/plugins/uart_terminal/scenes/uart_terminal_scene_text_input.c @@ -0,0 +1,60 @@ +#include "../uart_terminal_app_i.h" + +void uart_terminal_scene_text_input_callback(void* context) { + UART_TerminalApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventStartConsole); +} + +void uart_terminal_scene_text_input_on_enter(void* context) { + UART_TerminalApp* app = context; + + if(false == app->is_custom_tx_string) { + // Fill text input with selected string so that user can add to it + size_t length = strlen(app->selected_tx_string); + furi_assert(length < UART_TERMINAL_TEXT_INPUT_STORE_SIZE); + bzero(app->text_input_store, UART_TERMINAL_TEXT_INPUT_STORE_SIZE); + strncpy(app->text_input_store, app->selected_tx_string, length); + + // Add space - because flipper keyboard currently doesn't have a space + //app->text_input_store[length] = ' '; + app->text_input_store[length + 1] = '\0'; + app->is_custom_tx_string = true; + } + + // 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"); + uart_text_input_set_result_callback( + text_input, + uart_terminal_scene_text_input_callback, + app, + app->text_input_store, + UART_TERMINAL_TEXT_INPUT_STORE_SIZE, + false); + + view_dispatcher_switch_to_view(app->view_dispatcher, UART_TerminalAppViewTextInput); +} + +bool uart_terminal_scene_text_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_text_input_on_exit(void* context) { + UART_TerminalApp* app = context; + + uart_text_input_reset(app->text_input); +} diff --git a/applications/plugins/uart_terminal/uart_terminal.png b/applications/plugins/uart_terminal/uart_terminal.png new file mode 100644 index 000000000..8420f5692 Binary files /dev/null and b/applications/plugins/uart_terminal/uart_terminal.png differ diff --git a/applications/plugins/uart_terminal/uart_terminal_app.c b/applications/plugins/uart_terminal/uart_terminal_app.c new file mode 100644 index 000000000..2c18c5bae --- /dev/null +++ b/applications/plugins/uart_terminal/uart_terminal_app.c @@ -0,0 +1,104 @@ +#include "uart_terminal_app_i.h" + +#include +#include + +static bool uart_terminal_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + UART_TerminalApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool uart_terminal_app_back_event_callback(void* context) { + furi_assert(context); + UART_TerminalApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void uart_terminal_app_tick_event_callback(void* context) { + furi_assert(context); + UART_TerminalApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +UART_TerminalApp* uart_terminal_app_alloc() { + UART_TerminalApp* app = malloc(sizeof(UART_TerminalApp)); + + app->gui = furi_record_open(RECORD_GUI); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&uart_terminal_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, uart_terminal_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, uart_terminal_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, uart_terminal_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + UART_TerminalAppViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + for(int i = 0; i < NUM_MENU_ITEMS; ++i) { + app->selected_option_index[i] = 0; + } + + app->text_box = text_box_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, UART_TerminalAppViewConsoleOutput, text_box_get_view(app->text_box)); + app->text_box_store = furi_string_alloc(); + furi_string_reserve(app->text_box_store, UART_TERMINAL_TEXT_BOX_STORE_SIZE); + + app->text_input = uart_text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + UART_TerminalAppViewTextInput, + uart_text_input_get_view(app->text_input)); + + scene_manager_next_scene(app->scene_manager, UART_TerminalSceneStart); + + return app; +} + +void uart_terminal_app_free(UART_TerminalApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, UART_TerminalAppViewVarItemList); + view_dispatcher_remove_view(app->view_dispatcher, UART_TerminalAppViewConsoleOutput); + view_dispatcher_remove_view(app->view_dispatcher, UART_TerminalAppViewTextInput); + text_box_free(app->text_box); + furi_string_free(app->text_box_store); + uart_text_input_free(app->text_input); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + uart_terminal_uart_free(app->uart); + + // Close records + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t uart_terminal_app(void* p) { + UNUSED(p); + UART_TerminalApp* uart_terminal_app = uart_terminal_app_alloc(); + + uart_terminal_app->uart = uart_terminal_uart_init(uart_terminal_app); + + view_dispatcher_run(uart_terminal_app->view_dispatcher); + + uart_terminal_app_free(uart_terminal_app); + + return 0; +} diff --git a/applications/plugins/uart_terminal/uart_terminal_app.h b/applications/plugins/uart_terminal/uart_terminal_app.h new file mode 100644 index 000000000..c859d828b --- /dev/null +++ b/applications/plugins/uart_terminal/uart_terminal_app.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UART_TerminalApp UART_TerminalApp; + +#ifdef __cplusplus +} +#endif diff --git a/applications/plugins/uart_terminal/uart_terminal_app_i.h b/applications/plugins/uart_terminal/uart_terminal_app_i.h new file mode 100644 index 000000000..6b1996eb5 --- /dev/null +++ b/applications/plugins/uart_terminal/uart_terminal_app_i.h @@ -0,0 +1,49 @@ +#pragma once + +#include "uart_terminal_app.h" +#include "scenes/uart_terminal_scene.h" +#include "uart_terminal_custom_event.h" +#include "uart_terminal_uart.h" + +#include +#include +#include +#include +#include +#include "uart_text_input.h" + +#define NUM_MENU_ITEMS (4) + +#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; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + + char text_input_store[UART_TERMINAL_TEXT_INPUT_STORE_SIZE + 1]; + FuriString* text_box_store; + size_t text_box_store_strlen; + TextBox* text_box; + UART_TextInput* text_input; + + VariableItemList* var_item_list; + + UART_TerminalUart* uart; + int selected_menu_index; + int selected_option_index[NUM_MENU_ITEMS]; + const char* selected_tx_string; + bool is_command; + bool is_custom_tx_string; + bool focus_console_start; + bool show_stopscan_tip; + int BAUDRATE; +}; + +typedef enum { + UART_TerminalAppViewVarItemList, + UART_TerminalAppViewConsoleOutput, + UART_TerminalAppViewTextInput, +} UART_TerminalAppView; diff --git a/applications/plugins/uart_terminal/uart_terminal_custom_event.h b/applications/plugins/uart_terminal/uart_terminal_custom_event.h new file mode 100644 index 000000000..d57d822d1 --- /dev/null +++ b/applications/plugins/uart_terminal/uart_terminal_custom_event.h @@ -0,0 +1,7 @@ +#pragma once + +typedef enum { + UART_TerminalEventRefreshConsoleOutput = 0, + UART_TerminalEventStartConsole, + UART_TerminalEventStartKeyboard, +} UART_TerminalCustomEvent; diff --git a/applications/plugins/uart_terminal/uart_terminal_uart.c b/applications/plugins/uart_terminal/uart_terminal_uart.c new file mode 100644 index 000000000..136bd5d38 --- /dev/null +++ b/applications/plugins/uart_terminal/uart_terminal_uart.c @@ -0,0 +1,97 @@ +#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; + FuriStreamBuffer* rx_stream; + uint8_t rx_buf[RX_BUF_SIZE + 1]; + void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context); +}; + +typedef enum { + WorkerEvtStop = (1 << 0), + WorkerEvtRxDone = (1 << 1), +} WorkerEvtFlags; + +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)) { + furi_assert(uart); + uart->handle_rx_data_cb = handle_rx_data_cb; +} + +#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) + +void uart_terminal_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { + UART_TerminalUart* uart = (UART_TerminalUart*)context; + + if(ev == UartIrqEventRXNE) { + furi_stream_buffer_send(uart->rx_stream, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone); + } +} + +static int32_t uart_worker(void* context) { + UART_TerminalUart* uart = (void*)context; + + uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); + + while(1) { + uint32_t events = + furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); + furi_check((events & FuriFlagError) == 0); + if(events & WorkerEvtStop) break; + if(events & WorkerEvtRxDone) { + size_t len = furi_stream_buffer_receive(uart->rx_stream, uart->rx_buf, RX_BUF_SIZE, 0); + if(len > 0) { + if(uart->handle_rx_data_cb) uart->handle_rx_data_cb(uart->rx_buf, len, uart->app); + } + } + } + + furi_stream_buffer_free(uart->rx_stream); + + return 0; +} + +void uart_terminal_uart_tx(uint8_t* data, size_t len) { + furi_hal_uart_tx(UART_CH, data, len); +} + +UART_TerminalUart* uart_terminal_uart_init(UART_TerminalApp* app) { + UART_TerminalUart* uart = malloc(sizeof(UART_TerminalUart)); + + 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); + + uart->app = app; + uart->rx_thread = furi_thread_alloc(); + furi_thread_set_name(uart->rx_thread, "UART_TerminalUartRxThread"); + furi_thread_set_stack_size(uart->rx_thread, 1024); + furi_thread_set_context(uart->rx_thread, uart); + furi_thread_set_callback(uart->rx_thread, uart_worker); + + furi_thread_start(uart->rx_thread); + return uart; +} + +void uart_terminal_uart_free(UART_TerminalUart* uart) { + furi_assert(uart); + + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtStop); + 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(); + + free(uart); +} \ No newline at end of file diff --git a/applications/plugins/uart_terminal/uart_terminal_uart.h b/applications/plugins/uart_terminal/uart_terminal_uart.h new file mode 100644 index 000000000..ca95c92fb --- /dev/null +++ b/applications/plugins/uart_terminal/uart_terminal_uart.h @@ -0,0 +1,14 @@ +#pragma once + +#include "furi_hal.h" + +#define RX_BUF_SIZE (320) + +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); +UART_TerminalUart* uart_terminal_uart_init(UART_TerminalApp* app); +void uart_terminal_uart_free(UART_TerminalUart* uart); diff --git a/applications/plugins/uart_terminal/uart_text_input.c b/applications/plugins/uart_terminal/uart_text_input.c new file mode 100644 index 000000000..4a571b127 --- /dev/null +++ b/applications/plugins/uart_terminal/uart_text_input.c @@ -0,0 +1,637 @@ +#include "uart_text_input.h" +#include +#include "uart_terminal_icons.h" +#include + +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 = 1; +static const uint8_t keyboard_origin_y = 29; +static const uint8_t keyboard_row_count = 4; + +#define ENTER_KEY '\r' +#define BACKSPACE_KEY '\b' + +static const UART_TextInputKey keyboard_keys_row_1[] = { + {'{', 1, 0}, + {'(', 9, 0}, + {'[', 17, 0}, + {'|', 25, 0}, + {'@', 33, 0}, + {'&', 41, 0}, + {'#', 49, 0}, + {';', 57, 0}, + {'^', 65, 0}, + {'*', 73, 0}, + {'`', 81, 0}, + {'"', 89, 0}, + {'~', 97, 0}, + {'\'', 105, 0}, + {'.', 113, 0}, + {'/', 120, 0}, +}; + +static const UART_TextInputKey keyboard_keys_row_2[] = { + {'q', 1, 10}, + {'w', 9, 10}, + {'e', 17, 10}, + {'r', 25, 10}, + {'t', 33, 10}, + {'y', 41, 10}, + {'u', 49, 10}, + {'i', 57, 10}, + {'o', 65, 10}, + {'p', 73, 10}, + {'0', 81, 10}, + {'1', 89, 10}, + {'2', 97, 10}, + {'3', 105, 10}, + {'=', 113, 10}, + {'-', 120, 10}, +}; + +static const UART_TextInputKey keyboard_keys_row_3[] = { + {'a', 1, 21}, + {'s', 9, 21}, + {'d', 18, 21}, + {'f', 25, 21}, + {'g', 33, 21}, + {'h', 41, 21}, + {'j', 49, 21}, + {'k', 57, 21}, + {'l', 65, 21}, + {BACKSPACE_KEY, 72, 13}, + {'4', 89, 21}, + {'5', 97, 21}, + {'6', 105, 21}, + {'$', 113, 21}, + {'%', 120, 21}, + +}; + +static const UART_TextInputKey keyboard_keys_row_4[] = { + {'z', 1, 33}, + {'x', 9, 33}, + {'c', 18, 33}, + {'v', 25, 33}, + {'b', 33, 33}, + {'n', 41, 33}, + {'m', 49, 33}, + {'_', 57, 33}, + {ENTER_KEY, 64, 24}, + {'7', 89, 33}, + {'8', 97, 33}, + {'9', 105, 33}, + {'!', 113, 33}, + {'+', 120, 33}, +}; + +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; + case 3: + row_size = sizeof(keyboard_keys_row_3) / sizeof(UART_TextInputKey); + break; + case 4: + row_size = sizeof(keyboard_keys_row_4) / 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; + case 3: + row = keyboard_keys_row_3; + break; + case 4: + row = keyboard_keys_row_4; + break; + } + + return row; +} + +static char get_selected_char(UART_TextInputModel* model) { + return get_row(model->selected_row)[model->selected_column].text; +} + +static bool char_is_lowercase(char letter) { + return (letter >= 0x61 && letter <= 0x7A); +} + +static char char_to_uppercase(const char letter) { + switch(letter) { + case '_': + return 0x20; + break; + case '(': + return 0x29; + break; + case '{': + return 0x7d; + break; + case '[': + return 0x5d; + break; + case '/': + return 0x5c; + break; + case ';': + return 0x3a; + break; + case '.': + return 0x2c; + break; + case '!': + return 0x3f; + break; + case '<': + return 0x3e; + break; + } + if(isalpha(letter)) { + return (letter - 0x20); + } else { + return letter; + } +} + +static void uart_text_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_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 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); + } + + if(model->clear_default_text || + (text_length == 0 && char_is_lowercase(keys[column].text))) { + 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); + } else { + 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_text_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_text_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_text_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_text_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_text_input_handle_ok( + UART_TextInput* uart_text_input, + UART_TextInputModel* model, + bool shift) { + char selected = get_selected_char(model); + uint8_t text_length = strlen(model->text_buffer); + + if(shift) { + selected = char_to_uppercase(selected); + } + + 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_text_input_backspace_cb(model); + } else { + if(model->clear_default_text) { + 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; + } + } + model->clear_default_text = false; +} + +static bool uart_text_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_text_input_handle_up(uart_text_input, model); + break; + case InputKeyDown: + uart_text_input_handle_down(uart_text_input, model); + break; + case InputKeyLeft: + uart_text_input_handle_left(uart_text_input, model); + break; + case InputKeyRight: + uart_text_input_handle_right(uart_text_input, model); + break; + case InputKeyOk: + uart_text_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_text_input_handle_up(uart_text_input, model); + break; + case InputKeyDown: + uart_text_input_handle_down(uart_text_input, model); + break; + case InputKeyLeft: + uart_text_input_handle_left(uart_text_input, model); + break; + case InputKeyRight: + uart_text_input_handle_right(uart_text_input, model); + break; + case InputKeyOk: + uart_text_input_handle_ok(uart_text_input, model, true); + break; + case InputKeyBack: + uart_text_input_backspace_cb(model); + break; + default: + consumed = false; + break; + } + } else if(event->type == InputTypeRepeat) { + consumed = true; + switch(event->key) { + case InputKeyUp: + uart_text_input_handle_up(uart_text_input, model); + break; + case InputKeyDown: + uart_text_input_handle_down(uart_text_input, model); + break; + case InputKeyLeft: + uart_text_input_handle_left(uart_text_input, model); + break; + case InputKeyRight: + uart_text_input_handle_right(uart_text_input, model); + break; + case InputKeyBack: + uart_text_input_backspace_cb(model); + break; + default: + consumed = false; + break; + } + } + + // Commit model + view_commit_model(uart_text_input->view, consumed); + + return consumed; +} + +void uart_text_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_text_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_text_input_view_draw_callback); + view_set_input_callback(uart_text_input->view, uart_text_input_view_input_callback); + + uart_text_input->timer = + furi_timer_alloc(uart_text_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_text_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_text_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_text_input_get_view(UART_TextInput* uart_text_input) { + furi_assert(uart_text_input); + return uart_text_input->view; +} + +void uart_text_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 = 2; + model->selected_column = 8; + } + }, + true); +} + +void uart_text_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_text_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_text_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_text_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); +} diff --git a/applications/plugins/uart_terminal/uart_text_input.h b/applications/plugins/uart_terminal/uart_text_input.h new file mode 100644 index 000000000..f3703ed5a --- /dev/null +++ b/applications/plugins/uart_terminal/uart_text_input.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#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_text_input_alloc(); + +/** Deinitialize and free text input + * + * @param uart_text_input UART_TextInput instance + */ +void uart_text_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_text_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_text_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_text_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_text_input_set_validator( + UART_TextInput* uart_text_input, + UART_TextInputValidatorCallback callback, + void* callback_context); + +UART_TextInputValidatorCallback + uart_text_input_get_validator_callback(UART_TextInput* uart_text_input); + +void* uart_text_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_text_input_set_header_text(UART_TextInput* uart_text_input, const char* text); + +#ifdef __cplusplus +} +#endif diff --git a/applications/plugins/uart_terminal/uart_validators.c b/applications/plugins/uart_terminal/uart_validators.c new file mode 100644 index 000000000..c87a6cc6e --- /dev/null +++ b/applications/plugins/uart_terminal/uart_validators.c @@ -0,0 +1,57 @@ +#include +#include "uart_validators.h" +#include + +struct ValidatorIsFile { + char* app_path_folder; + const char* app_extension; + char* current_name; +}; + +bool validator_is_file_callback(const char* text, FuriString* error, void* context) { + furi_assert(context); + ValidatorIsFile* instance = context; + + if(instance->current_name != NULL) { + if(strcmp(instance->current_name, text) == 0) { + return true; + } + } + + bool ret = true; + FuriString* path = furi_string_alloc_printf( + "%s/%s%s", instance->app_path_folder, text, instance->app_extension); + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_common_stat(storage, furi_string_get_cstr(path), NULL) == FSE_OK) { + ret = false; + furi_string_printf(error, "This name\nexists!\nChoose\nanother one."); + } else { + ret = true; + } + furi_string_free(path); + furi_record_close(RECORD_STORAGE); + + return ret; +} + +ValidatorIsFile* validator_is_file_alloc_init( + const char* app_path_folder, + const char* app_extension, + const char* current_name) { + ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); + + instance->app_path_folder = strdup(app_path_folder); + instance->app_extension = app_extension; + if(current_name != NULL) { + instance->current_name = strdup(current_name); + } + + return instance; +} + +void validator_is_file_free(ValidatorIsFile* instance) { + furi_assert(instance); + free(instance->app_path_folder); + free(instance->current_name); + free(instance); +} diff --git a/applications/plugins/uart_terminal/uart_validators.h b/applications/plugins/uart_terminal/uart_validators.h new file mode 100644 index 000000000..d9200b6db --- /dev/null +++ b/applications/plugins/uart_terminal/uart_validators.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif +typedef struct ValidatorIsFile ValidatorIsFile; + +ValidatorIsFile* validator_is_file_alloc_init( + const char* app_path_folder, + const char* app_extension, + const char* current_name); + +void validator_is_file_free(ValidatorIsFile* instance); + +bool validator_is_file_callback(const char* text, FuriString* error, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/applications/plugins/unitemp/sensors/BMP180.c b/applications/plugins/unitemp/sensors/BMP180.c index e94f38044..aa0198289 100644 --- a/applications/plugins/unitemp/sensors/BMP180.c +++ b/applications/plugins/unitemp/sensors/BMP180.c @@ -101,8 +101,7 @@ bool unitemp_BMP180_init(Sensor* sensor) { bmp180_instance->bmp180_cal.MC = (buff[18] << 8) | buff[19]; bmp180_instance->bmp180_cal.MD = (buff[20] << 8) | buff[21]; - -UNITEMP_DEBUG( + UNITEMP_DEBUG( "Sensor BMP180 (0x%02X) calibration values: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", i2c_sensor->currentI2CAdr, bmp180_instance->bmp180_cal.AC1,