├── uart_terminal.png ├── assets ├── KeySave_24x11.png ├── KeyBackspace_16x9.png ├── KeySaveSelected_24x11.png ├── WarningDolphin_45x42.png └── KeyBackspaceSelected_16x9.png ├── scenes ├── uart_terminal_scene_config.h ├── uart_terminal_scene.h ├── uart_terminal_scene.c ├── uart_terminal_scene_text_input.c ├── uart_terminal_scene_start.c └── uart_terminal_scene_console_output.c ├── uart_terminal_app.h ├── uart_terminal_custom_event.h ├── .editorconfig ├── application.fam ├── uart_terminal_uart.h ├── uart_validators.h ├── LICENSE ├── .github └── workflows │ └── build.yml ├── uart_terminal_app_i.h ├── README.md ├── uart_validators.c ├── uart_text_input.h ├── uart_terminal_uart.c ├── uart_terminal_app.c ├── .clang-format ├── .gitignore └── uart_text_input.c /uart_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aafksab/lora_terminal/HEAD/uart_terminal.png -------------------------------------------------------------------------------- /assets/KeySave_24x11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aafksab/lora_terminal/HEAD/assets/KeySave_24x11.png -------------------------------------------------------------------------------- /assets/KeyBackspace_16x9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aafksab/lora_terminal/HEAD/assets/KeyBackspace_16x9.png -------------------------------------------------------------------------------- /assets/KeySaveSelected_24x11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aafksab/lora_terminal/HEAD/assets/KeySaveSelected_24x11.png -------------------------------------------------------------------------------- /assets/WarningDolphin_45x42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aafksab/lora_terminal/HEAD/assets/WarningDolphin_45x42.png -------------------------------------------------------------------------------- /assets/KeyBackspaceSelected_16x9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aafksab/lora_terminal/HEAD/assets/KeyBackspaceSelected_16x9.png -------------------------------------------------------------------------------- /scenes/uart_terminal_scene_config.h: -------------------------------------------------------------------------------- 1 | ADD_SCENE(uart_terminal, start, Start) 2 | ADD_SCENE(uart_terminal, console_output, ConsoleOutput) 3 | ADD_SCENE(uart_terminal, text_input, UART_TextInput) 4 | -------------------------------------------------------------------------------- /uart_terminal_app.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | typedef struct UART_TerminalApp UART_TerminalApp; 8 | 9 | #ifdef __cplusplus 10 | } 11 | #endif 12 | -------------------------------------------------------------------------------- /uart_terminal_custom_event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef enum { 4 | UART_TerminalEventRefreshConsoleOutput = 0, 5 | UART_TerminalEventStartConsole, 6 | UART_TerminalEventStartKeyboard, 7 | } UART_TerminalCustomEvent; 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | [*.{cpp,h,c,py,sh}] 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [{Makefile,*.mk}] 13 | indent_size = tab 14 | -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="LoRA_Terminal", 3 | name="LoRA_Terminal", 4 | apptype=FlipperAppType.EXTERNAL, 5 | entry_point="uart_terminal_app", 6 | requires=["gui"], 7 | stack_size=1 * 1024, 8 | order=90, 9 | fap_icon="uart_terminal.png", 10 | fap_category="GPIO", 11 | fap_icon_assets="assets", 12 | fap_icon_assets_symbol="lora_terminal", 13 | ) 14 | -------------------------------------------------------------------------------- /uart_terminal_uart.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "furi_hal.h" 4 | 5 | #define RX_BUF_SIZE (320) 6 | 7 | typedef struct UART_TerminalUart UART_TerminalUart; 8 | 9 | void uart_terminal_uart_set_handle_rx_data_cb( 10 | UART_TerminalUart* uart, 11 | void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context)); 12 | void uart_terminal_uart_tx(uint8_t* data, size_t len); 13 | UART_TerminalUart* uart_terminal_uart_init(UART_TerminalApp* app); 14 | void uart_terminal_uart_free(UART_TerminalUart* uart); 15 | -------------------------------------------------------------------------------- /uart_validators.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | typedef struct ValidatorIsFile ValidatorIsFile; 9 | 10 | ValidatorIsFile* validator_is_file_alloc_init( 11 | const char* app_path_folder, 12 | const char* app_extension, 13 | const char* current_name); 14 | 15 | void validator_is_file_free(ValidatorIsFile* instance); 16 | 17 | bool validator_is_file_callback(const char* text, FuriString* error, void* context); 18 | 19 | #ifdef __cplusplus 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /scenes/uart_terminal_scene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Generate scene id and total number 6 | #define ADD_SCENE(prefix, name, id) UART_TerminalScene##id, 7 | typedef enum { 8 | #include "uart_terminal_scene_config.h" 9 | UART_TerminalSceneNum, 10 | } UART_TerminalScene; 11 | #undef ADD_SCENE 12 | 13 | extern const SceneManagerHandlers uart_terminal_scene_handlers; 14 | 15 | // Generate scene on_enter handlers declaration 16 | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); 17 | #include "uart_terminal_scene_config.h" 18 | #undef ADD_SCENE 19 | 20 | // Generate scene on_event handlers declaration 21 | #define ADD_SCENE(prefix, name, id) \ 22 | bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); 23 | #include "uart_terminal_scene_config.h" 24 | #undef ADD_SCENE 25 | 26 | // Generate scene on_exit handlers declaration 27 | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); 28 | #include "uart_terminal_scene_config.h" 29 | #undef ADD_SCENE 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Malik cool4uma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /scenes/uart_terminal_scene.c: -------------------------------------------------------------------------------- 1 | #include "uart_terminal_scene.h" 2 | 3 | // Generate scene on_enter handlers array 4 | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, 5 | void (*const uart_terminal_scene_on_enter_handlers[])(void*) = { 6 | #include "uart_terminal_scene_config.h" 7 | }; 8 | #undef ADD_SCENE 9 | 10 | // Generate scene on_event handlers array 11 | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, 12 | bool (*const uart_terminal_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { 13 | #include "uart_terminal_scene_config.h" 14 | }; 15 | #undef ADD_SCENE 16 | 17 | // Generate scene on_exit handlers array 18 | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, 19 | void (*const uart_terminal_scene_on_exit_handlers[])(void* context) = { 20 | #include "uart_terminal_scene_config.h" 21 | }; 22 | #undef ADD_SCENE 23 | 24 | // Initialize scene handlers configuration structure 25 | const SceneManagerHandlers uart_terminal_scene_handlers = { 26 | .on_enter_handlers = uart_terminal_scene_on_enter_handlers, 27 | .on_event_handlers = uart_terminal_scene_on_event_handlers, 28 | .on_exit_handlers = uart_terminal_scene_on_exit_handlers, 29 | .scene_num = UART_TerminalSceneNum, 30 | }; 31 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: "FAP: Build for multiple SDK sources" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | schedule: 9 | # do a build every day 10 | - cron: "1 1 * * *" 11 | 12 | jobs: 13 | ufbt-build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | include: 18 | - name: dev channel 19 | sdk-channel: dev 20 | - name: release channel 21 | sdk-channel: release 22 | - name: Unleashed dev 23 | sdk-index-url: https://up.unleashedflip.com/directory.json 24 | sdk-channel: dev 25 | - name: Unleashed release 26 | sdk-index-url: https://up.unleashedflip.com/directory.json 27 | sdk-channel: release 28 | name: 'ufbt: Build for ${{ matrix.name }}' 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | - name: Build with ufbt 33 | uses: flipperdevices/flipperzero-ufbt-action@v0.1.1 34 | id: build-app 35 | with: 36 | sdk-channel: ${{ matrix.sdk-channel }} 37 | sdk-index-url: ${{ matrix.sdk-index-url }} 38 | - name: Upload app artifacts 39 | uses: actions/upload-artifact@v3 40 | with: 41 | # See ufbt action docs for other output variables 42 | name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }} 43 | path: ${{ steps.build-app.outputs.fap-artifacts }} 44 | -------------------------------------------------------------------------------- /uart_terminal_app_i.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "uart_terminal_app.h" 4 | #include "scenes/uart_terminal_scene.h" 5 | #include "uart_terminal_custom_event.h" 6 | #include "uart_terminal_uart.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "uart_text_input.h" 14 | 15 | #define NUM_MENU_ITEMS (4) 16 | 17 | #define UART_TERMINAL_TEXT_BOX_STORE_SIZE (4096) 18 | #define UART_TERMINAL_TEXT_INPUT_STORE_SIZE (512) 19 | #define UART_CH (FuriHalUartIdUSART1) 20 | 21 | struct UART_TerminalApp { 22 | Gui* gui; 23 | ViewDispatcher* view_dispatcher; 24 | SceneManager* scene_manager; 25 | 26 | char text_input_store[UART_TERMINAL_TEXT_INPUT_STORE_SIZE + 1]; 27 | FuriString* text_box_store; 28 | size_t text_box_store_strlen; 29 | TextBox* text_box; 30 | UART_TextInput* text_input; 31 | 32 | VariableItemList* var_item_list; 33 | 34 | UART_TerminalUart* uart; 35 | int selected_menu_index; 36 | int selected_option_index[NUM_MENU_ITEMS]; 37 | const char* selected_tx_string; 38 | bool is_command; 39 | bool is_custom_tx_string; 40 | bool focus_console_start; 41 | bool show_stopscan_tip; 42 | int BAUDRATE; 43 | }; 44 | 45 | typedef enum { 46 | UART_TerminalAppViewVarItemList, 47 | UART_TerminalAppViewConsoleOutput, 48 | UART_TerminalAppViewTextInput, 49 | } UART_TerminalAppView; 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LoRA Terminal for Flipper Zero 2 | [Flipper Zero](https://flipperzero.one/) app to control Lora Breakout board. 3 | 4 | ## Capabilities 5 | - Read log and command output by uart 6 | - Send commands by uart 7 | - Set baud rate 8 | - Fast commands 9 | 10 | ## Connecting 11 | | Flipper Zero pin | UART interface | 12 | | ---------------- | --------------- | 13 | | 13 TX | RX | 14 | | 14 RX | TX | 15 | |8, 18 GND | GND | 16 | 17 | Info: If possible, do not power your devices from 3V3 (pin 9) Flipper Zero. It does not support hot plugging. 18 | 19 | ## Keyboard 20 | LoRA_terminal uses its own special keyboard for work, which has all the symbols necessary for working in the console. 21 | 22 | To accommodate more characters on a small display, some characters are called up by holding. 23 | 24 | ![kbf](https://user-images.githubusercontent.com/122148894/212286637-7063f1ee-c6ff-46b9-8dc5-79a5f367fab1.png) 25 | 26 | 27 | ## How to install 28 | Copy the contents of the repository to the applications_user/LoRA_terminal folder Flipper Zero firmware and build app with the command ./fbt fap_LoRA-Term. 29 | 30 | Or use the tool [uFBT](https://github.com/flipperdevices/flipperzero-ufbt) for building applications for Flipper Zero. 31 | 32 | ## How it works 33 | 34 | 35 | ## INFO: 36 | Source code is taken from the [UART Terminal](https://github.com/cool4uma/UART_Terminal) project. Many thanks to the developers of the Wifi Marauder project and the UART Terminal project. 37 | -------------------------------------------------------------------------------- /uart_validators.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "uart_validators.h" 3 | #include 4 | 5 | struct ValidatorIsFile { 6 | char* app_path_folder; 7 | const char* app_extension; 8 | char* current_name; 9 | }; 10 | 11 | bool validator_is_file_callback(const char* text, FuriString* error, void* context) { 12 | furi_assert(context); 13 | ValidatorIsFile* instance = context; 14 | 15 | if(instance->current_name != NULL) { 16 | if(strcmp(instance->current_name, text) == 0) { 17 | return true; 18 | } 19 | } 20 | 21 | bool ret = true; 22 | FuriString* path = furi_string_alloc_printf( 23 | "%s/%s%s", instance->app_path_folder, text, instance->app_extension); 24 | Storage* storage = furi_record_open(RECORD_STORAGE); 25 | if(storage_common_stat(storage, furi_string_get_cstr(path), NULL) == FSE_OK) { 26 | ret = false; 27 | furi_string_printf(error, "This name\nexists!\nChoose\nanother one."); 28 | } else { 29 | ret = true; 30 | } 31 | furi_string_free(path); 32 | furi_record_close(RECORD_STORAGE); 33 | 34 | return ret; 35 | } 36 | 37 | ValidatorIsFile* validator_is_file_alloc_init( 38 | const char* app_path_folder, 39 | const char* app_extension, 40 | const char* current_name) { 41 | ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); 42 | 43 | instance->app_path_folder = strdup(app_path_folder); 44 | instance->app_extension = app_extension; 45 | if(current_name != NULL) { 46 | instance->current_name = strdup(current_name); 47 | } 48 | 49 | return instance; 50 | } 51 | 52 | void validator_is_file_free(ValidatorIsFile* instance) { 53 | furi_assert(instance); 54 | free(instance->app_path_folder); 55 | free(instance->current_name); 56 | free(instance); 57 | } 58 | -------------------------------------------------------------------------------- /scenes/uart_terminal_scene_text_input.c: -------------------------------------------------------------------------------- 1 | #include "../uart_terminal_app_i.h" 2 | 3 | void uart_terminal_scene_text_input_callback(void* context) { 4 | UART_TerminalApp* app = context; 5 | 6 | view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventStartConsole); 7 | } 8 | 9 | void uart_terminal_scene_text_input_on_enter(void* context) { 10 | UART_TerminalApp* app = context; 11 | 12 | if(false == app->is_custom_tx_string) { 13 | // Fill text input with selected string so that user can add to it 14 | size_t length = strlen(app->selected_tx_string); 15 | furi_assert(length < UART_TERMINAL_TEXT_INPUT_STORE_SIZE); 16 | bzero(app->text_input_store, UART_TERMINAL_TEXT_INPUT_STORE_SIZE); 17 | strncpy(app->text_input_store, app->selected_tx_string, length); 18 | 19 | // Add space - because flipper keyboard currently doesn't have a space 20 | //app->text_input_store[length] = ' '; 21 | app->text_input_store[length + 1] = '\0'; 22 | app->is_custom_tx_string = true; 23 | } 24 | 25 | // Setup view 26 | UART_TextInput* text_input = app->text_input; 27 | // Add help message to header 28 | uart_text_input_set_header_text(text_input, "Send command to UART"); 29 | uart_text_input_set_result_callback( 30 | text_input, 31 | uart_terminal_scene_text_input_callback, 32 | app, 33 | app->text_input_store, 34 | UART_TERMINAL_TEXT_INPUT_STORE_SIZE, 35 | false); 36 | 37 | view_dispatcher_switch_to_view(app->view_dispatcher, UART_TerminalAppViewTextInput); 38 | } 39 | 40 | bool uart_terminal_scene_text_input_on_event(void* context, SceneManagerEvent event) { 41 | UART_TerminalApp* app = context; 42 | bool consumed = false; 43 | 44 | if(event.type == SceneManagerEventTypeCustom) { 45 | if(event.event == UART_TerminalEventStartConsole) { 46 | // Point to custom string to send 47 | app->selected_tx_string = app->text_input_store; 48 | scene_manager_next_scene(app->scene_manager, UART_TerminalAppViewConsoleOutput); 49 | consumed = true; 50 | } 51 | } 52 | 53 | return consumed; 54 | } 55 | 56 | void uart_terminal_scene_text_input_on_exit(void* context) { 57 | UART_TerminalApp* app = context; 58 | 59 | uart_text_input_reset(app->text_input); 60 | } 61 | -------------------------------------------------------------------------------- /uart_text_input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "uart_validators.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | /** Text input anonymous structure */ 11 | typedef struct UART_TextInput UART_TextInput; 12 | typedef void (*UART_TextInputCallback)(void* context); 13 | typedef bool (*UART_TextInputValidatorCallback)(const char* text, FuriString* error, void* context); 14 | 15 | /** Allocate and initialize text input 16 | * 17 | * This text input is used to enter string 18 | * 19 | * @return UART_TextInput instance 20 | */ 21 | UART_TextInput* uart_text_input_alloc(); 22 | 23 | /** Deinitialize and free text input 24 | * 25 | * @param uart_text_input UART_TextInput instance 26 | */ 27 | void uart_text_input_free(UART_TextInput* uart_text_input); 28 | 29 | /** Clean text input view Note: this function does not free memory 30 | * 31 | * @param uart_text_input Text input instance 32 | */ 33 | void uart_text_input_reset(UART_TextInput* uart_text_input); 34 | 35 | /** Get text input view 36 | * 37 | * @param uart_text_input UART_TextInput instance 38 | * 39 | * @return View instance that can be used for embedding 40 | */ 41 | View* uart_text_input_get_view(UART_TextInput* uart_text_input); 42 | 43 | /** Set text input result callback 44 | * 45 | * @param uart_text_input UART_TextInput instance 46 | * @param callback callback fn 47 | * @param callback_context callback context 48 | * @param text_buffer pointer to YOUR text buffer, that we going 49 | * to modify 50 | * @param text_buffer_size YOUR text buffer size in bytes. Max string 51 | * length will be text_buffer_size-1. 52 | * @param clear_default_text clear text from text_buffer on first OK 53 | * event 54 | */ 55 | void uart_text_input_set_result_callback( 56 | UART_TextInput* uart_text_input, 57 | UART_TextInputCallback callback, 58 | void* callback_context, 59 | char* text_buffer, 60 | size_t text_buffer_size, 61 | bool clear_default_text); 62 | 63 | void uart_text_input_set_validator( 64 | UART_TextInput* uart_text_input, 65 | UART_TextInputValidatorCallback callback, 66 | void* callback_context); 67 | 68 | UART_TextInputValidatorCallback 69 | uart_text_input_get_validator_callback(UART_TextInput* uart_text_input); 70 | 71 | void* uart_text_input_get_validator_callback_context(UART_TextInput* uart_text_input); 72 | 73 | /** Set text input header text 74 | * 75 | * @param uart_text_input UART_TextInput instance 76 | * @param text text to be shown 77 | */ 78 | void uart_text_input_set_header_text(UART_TextInput* uart_text_input, const char* text); 79 | 80 | #ifdef __cplusplus 81 | } 82 | #endif 83 | -------------------------------------------------------------------------------- /uart_terminal_uart.c: -------------------------------------------------------------------------------- 1 | #include "uart_terminal_app_i.h" 2 | #include "uart_terminal_uart.h" 3 | 4 | //#define UART_CH (FuriHalUartIdUSART1) 5 | //#define BAUDRATE (115200) 6 | 7 | struct UART_TerminalUart { 8 | UART_TerminalApp* app; 9 | FuriThread* rx_thread; 10 | FuriStreamBuffer* rx_stream; 11 | uint8_t rx_buf[RX_BUF_SIZE + 1]; 12 | void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context); 13 | }; 14 | 15 | typedef enum { 16 | WorkerEvtStop = (1 << 0), 17 | WorkerEvtRxDone = (1 << 1), 18 | } WorkerEvtFlags; 19 | 20 | void uart_terminal_uart_set_handle_rx_data_cb( 21 | UART_TerminalUart* uart, 22 | void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context)) { 23 | furi_assert(uart); 24 | uart->handle_rx_data_cb = handle_rx_data_cb; 25 | } 26 | 27 | #define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) 28 | 29 | void uart_terminal_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { 30 | UART_TerminalUart* uart = (UART_TerminalUart*)context; 31 | 32 | if(ev == UartIrqEventRXNE) { 33 | furi_stream_buffer_send(uart->rx_stream, &data, 1, 0); 34 | furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone); 35 | } 36 | } 37 | 38 | static int32_t uart_worker(void* context) { 39 | UART_TerminalUart* uart = (void*)context; 40 | 41 | uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); 42 | 43 | while(1) { 44 | uint32_t events = 45 | furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); 46 | furi_check((events & FuriFlagError) == 0); 47 | if(events & WorkerEvtStop) break; 48 | if(events & WorkerEvtRxDone) { 49 | size_t len = furi_stream_buffer_receive(uart->rx_stream, uart->rx_buf, RX_BUF_SIZE, 0); 50 | if(len > 0) { 51 | if(uart->handle_rx_data_cb) uart->handle_rx_data_cb(uart->rx_buf, len, uart->app); 52 | } 53 | } 54 | } 55 | 56 | furi_stream_buffer_free(uart->rx_stream); 57 | 58 | return 0; 59 | } 60 | 61 | void uart_terminal_uart_tx(uint8_t* data, size_t len) { 62 | furi_hal_uart_tx(UART_CH, data, len); 63 | } 64 | 65 | UART_TerminalUart* uart_terminal_uart_init(UART_TerminalApp* app) { 66 | UART_TerminalUart* uart = malloc(sizeof(UART_TerminalUart)); 67 | 68 | furi_hal_console_disable(); 69 | if(app->BAUDRATE == 0) { 70 | app->BAUDRATE = 115200; 71 | } 72 | furi_hal_uart_set_br(UART_CH, app->BAUDRATE); 73 | furi_hal_uart_set_irq_cb(UART_CH, uart_terminal_uart_on_irq_cb, uart); 74 | 75 | uart->app = app; 76 | uart->rx_thread = furi_thread_alloc(); 77 | furi_thread_set_name(uart->rx_thread, "UART_TerminalUartRxThread"); 78 | furi_thread_set_stack_size(uart->rx_thread, 1024); 79 | furi_thread_set_context(uart->rx_thread, uart); 80 | furi_thread_set_callback(uart->rx_thread, uart_worker); 81 | 82 | furi_thread_start(uart->rx_thread); 83 | return uart; 84 | } 85 | 86 | void uart_terminal_uart_free(UART_TerminalUart* uart) { 87 | furi_assert(uart); 88 | 89 | furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtStop); 90 | furi_thread_join(uart->rx_thread); 91 | furi_thread_free(uart->rx_thread); 92 | 93 | furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL); 94 | furi_hal_console_enable(); 95 | 96 | free(uart); 97 | } -------------------------------------------------------------------------------- /uart_terminal_app.c: -------------------------------------------------------------------------------- 1 | #include "uart_terminal_app_i.h" 2 | 3 | #include 4 | #include 5 | 6 | static bool uart_terminal_app_custom_event_callback(void* context, uint32_t event) { 7 | furi_assert(context); 8 | UART_TerminalApp* app = context; 9 | return scene_manager_handle_custom_event(app->scene_manager, event); 10 | } 11 | 12 | static bool uart_terminal_app_back_event_callback(void* context) { 13 | furi_assert(context); 14 | UART_TerminalApp* app = context; 15 | return scene_manager_handle_back_event(app->scene_manager); 16 | } 17 | 18 | static void uart_terminal_app_tick_event_callback(void* context) { 19 | furi_assert(context); 20 | UART_TerminalApp* app = context; 21 | scene_manager_handle_tick_event(app->scene_manager); 22 | } 23 | 24 | UART_TerminalApp* uart_terminal_app_alloc() { 25 | UART_TerminalApp* app = malloc(sizeof(UART_TerminalApp)); 26 | 27 | app->gui = furi_record_open(RECORD_GUI); 28 | 29 | app->view_dispatcher = view_dispatcher_alloc(); 30 | app->scene_manager = scene_manager_alloc(&uart_terminal_scene_handlers, app); 31 | view_dispatcher_enable_queue(app->view_dispatcher); 32 | view_dispatcher_set_event_callback_context(app->view_dispatcher, app); 33 | 34 | view_dispatcher_set_custom_event_callback( 35 | app->view_dispatcher, uart_terminal_app_custom_event_callback); 36 | view_dispatcher_set_navigation_event_callback( 37 | app->view_dispatcher, uart_terminal_app_back_event_callback); 38 | view_dispatcher_set_tick_event_callback( 39 | app->view_dispatcher, uart_terminal_app_tick_event_callback, 100); 40 | 41 | view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); 42 | 43 | app->var_item_list = variable_item_list_alloc(); 44 | view_dispatcher_add_view( 45 | app->view_dispatcher, 46 | UART_TerminalAppViewVarItemList, 47 | variable_item_list_get_view(app->var_item_list)); 48 | 49 | for(int i = 0; i < NUM_MENU_ITEMS; ++i) { 50 | app->selected_option_index[i] = 0; 51 | } 52 | 53 | app->text_box = text_box_alloc(); 54 | view_dispatcher_add_view( 55 | app->view_dispatcher, UART_TerminalAppViewConsoleOutput, text_box_get_view(app->text_box)); 56 | app->text_box_store = furi_string_alloc(); 57 | furi_string_reserve(app->text_box_store, UART_TERMINAL_TEXT_BOX_STORE_SIZE); 58 | 59 | app->text_input = uart_text_input_alloc(); 60 | view_dispatcher_add_view( 61 | app->view_dispatcher, 62 | UART_TerminalAppViewTextInput, 63 | uart_text_input_get_view(app->text_input)); 64 | 65 | scene_manager_next_scene(app->scene_manager, UART_TerminalSceneStart); 66 | 67 | return app; 68 | } 69 | 70 | void uart_terminal_app_free(UART_TerminalApp* app) { 71 | furi_assert(app); 72 | 73 | // Views 74 | view_dispatcher_remove_view(app->view_dispatcher, UART_TerminalAppViewVarItemList); 75 | view_dispatcher_remove_view(app->view_dispatcher, UART_TerminalAppViewConsoleOutput); 76 | view_dispatcher_remove_view(app->view_dispatcher, UART_TerminalAppViewTextInput); 77 | text_box_free(app->text_box); 78 | furi_string_free(app->text_box_store); 79 | uart_text_input_free(app->text_input); 80 | 81 | // View dispatcher 82 | view_dispatcher_free(app->view_dispatcher); 83 | scene_manager_free(app->scene_manager); 84 | 85 | uart_terminal_uart_free(app->uart); 86 | 87 | // Close records 88 | furi_record_close(RECORD_GUI); 89 | 90 | free(app); 91 | } 92 | 93 | int32_t uart_terminal_app(void* p) { 94 | UNUSED(p); 95 | UART_TerminalApp* uart_terminal_app = uart_terminal_app_alloc(); 96 | 97 | uart_terminal_app->uart = uart_terminal_uart_init(uart_terminal_app); 98 | 99 | view_dispatcher_run(uart_terminal_app->view_dispatcher); 100 | 101 | uart_terminal_app_free(uart_terminal_app); 102 | 103 | return 0; 104 | } 105 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: AlwaysBreak 5 | AlignArrayOfStructures: None 6 | AlignConsecutiveMacros: None 7 | AlignConsecutiveAssignments: None 8 | AlignConsecutiveBitFields: None 9 | AlignConsecutiveDeclarations: None 10 | AlignEscapedNewlines: Left 11 | AlignOperands: Align 12 | AlignTrailingComments: false 13 | AllowAllArgumentsOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: false 15 | AllowShortEnumsOnASingleLine: true 16 | AllowShortBlocksOnASingleLine: Never 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: None 19 | AllowShortLambdasOnASingleLine: All 20 | AllowShortIfStatementsOnASingleLine: WithoutElse 21 | AllowShortLoopsOnASingleLine: true 22 | AlwaysBreakAfterDefinitionReturnType: None 23 | AlwaysBreakAfterReturnType: None 24 | AlwaysBreakBeforeMultilineStrings: false 25 | AlwaysBreakTemplateDeclarations: Yes 26 | AttributeMacros: 27 | - __capability 28 | BinPackArguments: false 29 | BinPackParameters: false 30 | BraceWrapping: 31 | AfterCaseLabel: false 32 | AfterClass: false 33 | AfterControlStatement: Never 34 | AfterEnum: false 35 | AfterFunction: false 36 | AfterNamespace: false 37 | AfterObjCDeclaration: false 38 | AfterStruct: false 39 | AfterUnion: false 40 | AfterExternBlock: false 41 | BeforeCatch: false 42 | BeforeElse: false 43 | BeforeLambdaBody: false 44 | BeforeWhile: false 45 | IndentBraces: false 46 | SplitEmptyFunction: true 47 | SplitEmptyRecord: true 48 | SplitEmptyNamespace: true 49 | BreakBeforeBinaryOperators: None 50 | BreakBeforeConceptDeclarations: true 51 | BreakBeforeBraces: Attach 52 | BreakBeforeInheritanceComma: false 53 | BreakInheritanceList: BeforeColon 54 | BreakBeforeTernaryOperators: false 55 | BreakConstructorInitializersBeforeComma: false 56 | BreakConstructorInitializers: BeforeComma 57 | BreakAfterJavaFieldAnnotations: false 58 | BreakStringLiterals: false 59 | ColumnLimit: 99 60 | CommentPragmas: '^ IWYU pragma:' 61 | QualifierAlignment: Leave 62 | CompactNamespaces: false 63 | ConstructorInitializerIndentWidth: 4 64 | ContinuationIndentWidth: 4 65 | Cpp11BracedListStyle: true 66 | DeriveLineEnding: true 67 | DerivePointerAlignment: false 68 | DisableFormat: false 69 | EmptyLineAfterAccessModifier: Never 70 | EmptyLineBeforeAccessModifier: LogicalBlock 71 | ExperimentalAutoDetectBinPacking: false 72 | PackConstructorInitializers: BinPack 73 | BasedOnStyle: '' 74 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 75 | AllowAllConstructorInitializersOnNextLine: true 76 | FixNamespaceComments: false 77 | ForEachMacros: 78 | - foreach 79 | - Q_FOREACH 80 | - BOOST_FOREACH 81 | IfMacros: 82 | - KJ_IF_MAYBE 83 | IncludeBlocks: Preserve 84 | IncludeCategories: 85 | - Regex: '.*' 86 | Priority: 1 87 | SortPriority: 0 88 | CaseSensitive: false 89 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 90 | Priority: 3 91 | SortPriority: 0 92 | CaseSensitive: false 93 | - Regex: '.*' 94 | Priority: 1 95 | SortPriority: 0 96 | CaseSensitive: false 97 | IncludeIsMainRegex: '(Test)?$' 98 | IncludeIsMainSourceRegex: '' 99 | IndentAccessModifiers: false 100 | IndentCaseLabels: false 101 | IndentCaseBlocks: false 102 | IndentGotoLabels: true 103 | IndentPPDirectives: None 104 | IndentExternBlock: AfterExternBlock 105 | IndentRequires: false 106 | IndentWidth: 4 107 | IndentWrappedFunctionNames: true 108 | InsertTrailingCommas: None 109 | JavaScriptQuotes: Leave 110 | JavaScriptWrapImports: true 111 | KeepEmptyLinesAtTheStartOfBlocks: false 112 | LambdaBodyIndentation: Signature 113 | MacroBlockBegin: '' 114 | MacroBlockEnd: '' 115 | MaxEmptyLinesToKeep: 1 116 | NamespaceIndentation: None 117 | ObjCBinPackProtocolList: Auto 118 | ObjCBlockIndentWidth: 4 119 | ObjCBreakBeforeNestedBlockParam: true 120 | ObjCSpaceAfterProperty: true 121 | ObjCSpaceBeforeProtocolList: true 122 | PenaltyBreakAssignment: 10 123 | PenaltyBreakBeforeFirstCallParameter: 30 124 | PenaltyBreakComment: 10 125 | PenaltyBreakFirstLessLess: 0 126 | PenaltyBreakOpenParenthesis: 0 127 | PenaltyBreakString: 10 128 | PenaltyBreakTemplateDeclaration: 10 129 | PenaltyExcessCharacter: 100 130 | PenaltyReturnTypeOnItsOwnLine: 60 131 | PenaltyIndentedWhitespace: 0 132 | PointerAlignment: Left 133 | PPIndentWidth: -1 134 | ReferenceAlignment: Pointer 135 | ReflowComments: false 136 | RemoveBracesLLVM: false 137 | SeparateDefinitionBlocks: Leave 138 | ShortNamespaceLines: 1 139 | SortIncludes: Never 140 | SortJavaStaticImport: Before 141 | SortUsingDeclarations: false 142 | SpaceAfterCStyleCast: false 143 | SpaceAfterLogicalNot: false 144 | SpaceAfterTemplateKeyword: true 145 | SpaceBeforeAssignmentOperators: true 146 | SpaceBeforeCaseColon: false 147 | SpaceBeforeCpp11BracedList: false 148 | SpaceBeforeCtorInitializerColon: true 149 | SpaceBeforeInheritanceColon: true 150 | SpaceBeforeParens: Never 151 | SpaceBeforeParensOptions: 152 | AfterControlStatements: false 153 | AfterForeachMacros: false 154 | AfterFunctionDefinitionName: false 155 | AfterFunctionDeclarationName: false 156 | AfterIfMacros: false 157 | AfterOverloadedOperator: false 158 | BeforeNonEmptyParentheses: false 159 | SpaceAroundPointerQualifiers: Default 160 | SpaceBeforeRangeBasedForLoopColon: true 161 | SpaceInEmptyBlock: false 162 | SpaceInEmptyParentheses: false 163 | SpacesBeforeTrailingComments: 1 164 | SpacesInAngles: Never 165 | SpacesInConditionalStatement: false 166 | SpacesInContainerLiterals: false 167 | SpacesInCStyleCastParentheses: false 168 | SpacesInLineCommentPrefix: 169 | Minimum: 1 170 | Maximum: -1 171 | SpacesInParentheses: false 172 | SpacesInSquareBrackets: false 173 | SpaceBeforeSquareBrackets: false 174 | BitFieldColonSpacing: Both 175 | Standard: c++03 176 | StatementAttributeLikeMacros: 177 | - Q_EMIT 178 | StatementMacros: 179 | - Q_UNUSED 180 | - QT_REQUIRE_VERSION 181 | TabWidth: 4 182 | UseCRLF: false 183 | UseTab: Never 184 | WhitespaceSensitiveMacros: 185 | - STRINGIZE 186 | - PP_STRINGIZE 187 | - BOOST_PP_STRINGIZE 188 | - NS_SWIFT_NAME 189 | - CF_SWIFT_NAME 190 | ... 191 | 192 | -------------------------------------------------------------------------------- /scenes/uart_terminal_scene_start.c: -------------------------------------------------------------------------------- 1 | #include "../uart_terminal_app_i.h" 2 | #include 3 | 4 | // For each command, define whether additional arguments are needed 5 | // (enabling text input to fill them out), and whether the console 6 | // text box should focus at the start of the output or the end 7 | typedef enum { NO_ARGS = 0, INPUT_ARGS, TOGGLE_ARGS } InputArgs; 8 | 9 | typedef enum { FOCUS_CONSOLE_END = 0, FOCUS_CONSOLE_START, FOCUS_CONSOLE_TOGGLE } FocusConsole; 10 | 11 | #define SHOW_STOPSCAN_TIP (true) 12 | #define NO_TIP (false) 13 | 14 | #define MAX_OPTIONS (9) 15 | typedef struct { 16 | const char* item_string; 17 | const char* options_menu[MAX_OPTIONS]; 18 | int num_options_menu; 19 | const char* actual_commands[MAX_OPTIONS]; 20 | InputArgs needs_keyboard; 21 | FocusConsole focus_console; 22 | bool show_stopscan_tip; 23 | } UART_TerminalItem; 24 | 25 | // NUM_MENU_ITEMS defined in uart_terminal_app_i.h - if you add an entry here, increment it! 26 | const UART_TerminalItem items[NUM_MENU_ITEMS] = { 27 | {"Console", 28 | {"115200", "2400", "9600", "19200", "38400", "57600", "230400", "460800", "921600"}, 29 | 9, 30 | {"115200", "2400", "9600", "19200", "38400", "57600", "230400", "460800", "921600"}, 31 | NO_ARGS, 32 | FOCUS_CONSOLE_TOGGLE, 33 | NO_TIP}, 34 | {"Send command", {""}, 1, {""}, INPUT_ARGS, FOCUS_CONSOLE_END, NO_TIP}, 35 | {"Fast cmd", 36 | {"AT", "Param", "Band", "ADDRESS", "NETWORKID", "CRFOP", "SEND", "RCV", "FACTORY"}, 37 | 9, 38 | {"AT", 39 | "AT+PARAMETER?", 40 | "AT+BAND?", 41 | "AT+ADDRESS?", 42 | "AT+NETWORKID?", 43 | "AT+CRFOP?", 44 | "AT+SEND=0,24,HELLO_WORLD", 45 | "+RCV", 46 | "AT+FACTORY"}, 47 | INPUT_ARGS, 48 | FOCUS_CONSOLE_END, 49 | NO_TIP}, 50 | {"Help", {""}, 1, {"help"}, NO_ARGS, FOCUS_CONSOLE_START, SHOW_STOPSCAN_TIP}, 51 | }; 52 | 53 | static void uart_terminal_scene_start_var_list_enter_callback(void* context, uint32_t index) { 54 | furi_assert(context); 55 | UART_TerminalApp* app = context; 56 | 57 | furi_assert(index < NUM_MENU_ITEMS); 58 | const UART_TerminalItem* item = &items[index]; 59 | 60 | const int selected_option_index = app->selected_option_index[index]; 61 | furi_assert(selected_option_index < item->num_options_menu); 62 | app->selected_tx_string = item->actual_commands[selected_option_index]; 63 | app->is_command = (1 <= index); 64 | app->is_custom_tx_string = false; 65 | app->selected_menu_index = index; 66 | app->focus_console_start = (item->focus_console == FOCUS_CONSOLE_TOGGLE) ? 67 | (selected_option_index == 0) : 68 | item->focus_console; 69 | app->show_stopscan_tip = item->show_stopscan_tip; 70 | 71 | bool needs_keyboard = (item->needs_keyboard == TOGGLE_ARGS) ? (selected_option_index != 0) : 72 | item->needs_keyboard; 73 | if(needs_keyboard) { 74 | view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventStartKeyboard); 75 | } else { 76 | view_dispatcher_send_custom_event(app->view_dispatcher, UART_TerminalEventStartConsole); 77 | } 78 | } 79 | 80 | static void uart_terminal_scene_start_var_list_change_callback(VariableItem* item) { 81 | furi_assert(item); 82 | 83 | UART_TerminalApp* app = variable_item_get_context(item); 84 | furi_assert(app); 85 | 86 | const UART_TerminalItem* menu_item = &items[app->selected_menu_index]; 87 | uint8_t item_index = variable_item_get_current_value_index(item); 88 | furi_assert(item_index < menu_item->num_options_menu); 89 | variable_item_set_current_value_text(item, menu_item->options_menu[item_index]); 90 | app->selected_option_index[app->selected_menu_index] = item_index; 91 | } 92 | 93 | void uart_terminal_scene_start_on_enter(void* context) { 94 | UART_TerminalApp* app = context; 95 | VariableItemList* var_item_list = app->var_item_list; 96 | 97 | variable_item_list_set_enter_callback( 98 | var_item_list, uart_terminal_scene_start_var_list_enter_callback, app); 99 | 100 | VariableItem* item; 101 | for(int i = 0; i < NUM_MENU_ITEMS; ++i) { 102 | item = variable_item_list_add( 103 | var_item_list, 104 | items[i].item_string, 105 | items[i].num_options_menu, 106 | uart_terminal_scene_start_var_list_change_callback, 107 | app); 108 | variable_item_set_current_value_index(item, app->selected_option_index[i]); 109 | variable_item_set_current_value_text( 110 | item, items[i].options_menu[app->selected_option_index[i]]); 111 | } 112 | 113 | variable_item_list_set_selected_item( 114 | var_item_list, scene_manager_get_scene_state(app->scene_manager, UART_TerminalSceneStart)); 115 | 116 | view_dispatcher_switch_to_view(app->view_dispatcher, UART_TerminalAppViewVarItemList); 117 | } 118 | 119 | bool uart_terminal_scene_start_on_event(void* context, SceneManagerEvent event) { 120 | UNUSED(context); 121 | UART_TerminalApp* app = context; 122 | bool consumed = false; 123 | 124 | if(event.type == SceneManagerEventTypeCustom) { 125 | if(event.event == UART_TerminalEventStartKeyboard) { 126 | scene_manager_set_scene_state( 127 | app->scene_manager, UART_TerminalSceneStart, app->selected_menu_index); 128 | scene_manager_next_scene(app->scene_manager, UART_TerminalAppViewTextInput); 129 | } else if(event.event == UART_TerminalEventStartConsole) { 130 | scene_manager_set_scene_state( 131 | app->scene_manager, UART_TerminalSceneStart, app->selected_menu_index); 132 | scene_manager_next_scene(app->scene_manager, UART_TerminalAppViewConsoleOutput); 133 | } 134 | consumed = true; 135 | } else if(event.type == SceneManagerEventTypeTick) { 136 | app->selected_menu_index = variable_item_list_get_selected_item_index(app->var_item_list); 137 | consumed = true; 138 | } 139 | 140 | return consumed; 141 | } 142 | 143 | void uart_terminal_scene_start_on_exit(void* context) { 144 | UART_TerminalApp* app = context; 145 | variable_item_list_reset(app->var_item_list); 146 | } 147 | -------------------------------------------------------------------------------- /scenes/uart_terminal_scene_console_output.c: -------------------------------------------------------------------------------- 1 | #include "../uart_terminal_app_i.h" 2 | #include 3 | #include 4 | #define appName "lora_terminal" 5 | 6 | void uart_terminal_console_output_handle_rx_data_cb(uint8_t* buf, size_t len, void* context) { 7 | furi_assert(context); 8 | UART_TerminalApp* app = context; 9 | 10 | // If text box store gets too big, then truncate it 11 | app->text_box_store_strlen += len; 12 | if(app->text_box_store_strlen >= UART_TERMINAL_TEXT_BOX_STORE_SIZE - 1) { 13 | furi_string_right(app->text_box_store, app->text_box_store_strlen / 2); 14 | app->text_box_store_strlen = furi_string_size(app->text_box_store) + len; 15 | } 16 | 17 | // Null-terminate buf and append to text box store 18 | buf[len] = '\0'; 19 | furi_string_cat_printf(app->text_box_store, "%s", buf); 20 | 21 | view_dispatcher_send_custom_event( 22 | app->view_dispatcher, UART_TerminalEventRefreshConsoleOutput); 23 | } 24 | 25 | void uart_terminal_scene_console_output_on_enter(void* context) { 26 | UART_TerminalApp* app = context; 27 | 28 | TextBox* text_box = app->text_box; 29 | text_box_reset(app->text_box); 30 | text_box_set_font(text_box, TextBoxFontText); 31 | if(app->focus_console_start) { 32 | text_box_set_focus(text_box, TextBoxFocusStart); 33 | } else { 34 | text_box_set_focus(text_box, TextBoxFocusEnd); 35 | } 36 | 37 | //Change baudrate /////////////////////////////////////////////////////////////////////////// 38 | if(0 == strncmp("2400", app->selected_tx_string, strlen("2400")) && app->BAUDRATE != 2400) { 39 | uart_terminal_uart_free(app->uart); 40 | app->BAUDRATE = 2400; 41 | app->uart = uart_terminal_uart_init(app); 42 | } 43 | if(0 == strncmp("9600", app->selected_tx_string, strlen("9600")) && app->BAUDRATE != 9600) { 44 | uart_terminal_uart_free(app->uart); 45 | app->BAUDRATE = 9600; 46 | app->uart = uart_terminal_uart_init(app); 47 | } 48 | if(0 == strncmp("19200", app->selected_tx_string, strlen("19200")) && app->BAUDRATE != 19200) { 49 | uart_terminal_uart_free(app->uart); 50 | app->BAUDRATE = 19200; 51 | app->uart = uart_terminal_uart_init(app); 52 | } 53 | if(0 == strncmp("38400", app->selected_tx_string, strlen("38400")) && app->BAUDRATE != 38400) { 54 | uart_terminal_uart_free(app->uart); 55 | app->BAUDRATE = 38400; 56 | app->uart = uart_terminal_uart_init(app); 57 | } 58 | if(0 == strncmp("57600", app->selected_tx_string, strlen("57600")) && app->BAUDRATE != 57600) { 59 | uart_terminal_uart_free(app->uart); 60 | app->BAUDRATE = 57600; 61 | app->uart = uart_terminal_uart_init(app); 62 | } 63 | if(0 == strncmp("115200", app->selected_tx_string, strlen("115200")) && 64 | app->BAUDRATE != 115200) { 65 | uart_terminal_uart_free(app->uart); 66 | app->BAUDRATE = 115200; 67 | app->uart = uart_terminal_uart_init(app); 68 | } 69 | if(0 == strncmp("230400", app->selected_tx_string, strlen("230400")) && 70 | app->BAUDRATE != 230400) { 71 | uart_terminal_uart_free(app->uart); 72 | app->BAUDRATE = 230400; 73 | app->uart = uart_terminal_uart_init(app); 74 | } 75 | if(0 == strncmp("460800", app->selected_tx_string, strlen("460800")) && 76 | app->BAUDRATE != 460800) { 77 | uart_terminal_uart_free(app->uart); 78 | app->BAUDRATE = 460800; 79 | app->uart = uart_terminal_uart_init(app); 80 | } 81 | if(0 == strncmp("921600", app->selected_tx_string, strlen("921600")) && 82 | app->BAUDRATE != 921600) { 83 | uart_terminal_uart_free(app->uart); 84 | app->BAUDRATE = 921600; 85 | app->uart = uart_terminal_uart_init(app); 86 | } 87 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 88 | 89 | if(app->is_command) { 90 | furi_string_reset(app->text_box_store); 91 | app->text_box_store_strlen = 0; 92 | 93 | if(0 == strncmp("help", app->selected_tx_string, strlen("help"))) { 94 | const char* help_msg = 95 | "LoRA terminal for Flipper\n\nI'm on github: aafksab\n\nThis app is a modified\nUART Terminal,\nThanks cool4uma(github)\nfor great code and app.\n\n"; 96 | furi_string_cat_str(app->text_box_store, help_msg); 97 | app->text_box_store_strlen += strlen(help_msg); 98 | } 99 | 100 | if(app->show_stopscan_tip) { 101 | const char* help_msg = "Press BACK to return\n"; 102 | furi_string_cat_str(app->text_box_store, help_msg); 103 | app->text_box_store_strlen += strlen(help_msg); 104 | } 105 | } 106 | 107 | // Set starting text - for "View Log", this will just be what was already in the text box store 108 | text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); 109 | 110 | scene_manager_set_scene_state(app->scene_manager, UART_TerminalSceneConsoleOutput, 0); 111 | view_dispatcher_switch_to_view(app->view_dispatcher, UART_TerminalAppViewConsoleOutput); 112 | 113 | // Register callback to receive data 114 | uart_terminal_uart_set_handle_rx_data_cb( 115 | app->uart, uart_terminal_console_output_handle_rx_data_cb); // setup callback for rx thread 116 | 117 | // Send command with CR+LF 118 | if(app->is_command && app->selected_tx_string) { 119 | char buffer[240]; 120 | snprintf(buffer, 240, "%s\r\n", (app->selected_tx_string)); 121 | FURI_LOG_E(appName, "Command: %s", buffer); 122 | FURI_LOG_I(appName, "Command: %s", buffer); 123 | uart_terminal_uart_tx((unsigned char*)buffer, strlen(buffer)); 124 | } 125 | } 126 | 127 | bool uart_terminal_scene_console_output_on_event(void* context, SceneManagerEvent event) { 128 | UART_TerminalApp* app = context; 129 | 130 | bool consumed = false; 131 | 132 | if(event.type == SceneManagerEventTypeCustom) { 133 | text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); 134 | consumed = true; 135 | } else if(event.type == SceneManagerEventTypeTick) { 136 | consumed = true; 137 | } 138 | 139 | return consumed; 140 | } 141 | 142 | void uart_terminal_scene_console_output_on_exit(void* context) { 143 | UART_TerminalApp* app = context; 144 | 145 | // Unregister rx callback 146 | uart_terminal_uart_set_handle_rx_data_cb(app->uart, NULL); 147 | 148 | // Automatically logut when exiting view 149 | //if(app->is_command) { 150 | // uart_terminal_uart_tx((uint8_t*)("exit\n"), strlen("exit\n")); 151 | //} 152 | } 153 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/cmake,c,c++,windows,linux,visualstudio,visualstudiocode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=cmake,c,c++,windows,linux,visualstudio,visualstudiocode 3 | 4 | ### C ### 5 | # Prerequisites 6 | *.d 7 | 8 | # Object files 9 | *.o 10 | *.ko 11 | *.obj 12 | *.elf 13 | 14 | # Linker output 15 | *.ilk 16 | *.map 17 | *.exp 18 | 19 | # Precompiled Headers 20 | *.gch 21 | *.pch 22 | 23 | # Libraries 24 | *.lib 25 | *.a 26 | *.la 27 | *.lo 28 | 29 | # Shared objects (inc. Windows DLLs) 30 | *.dll 31 | *.so 32 | *.so.* 33 | *.dylib 34 | 35 | # Executables 36 | *.exe 37 | *.out 38 | *.app 39 | *.i*86 40 | *.x86_64 41 | *.hex 42 | 43 | # Debug files 44 | *.dSYM/ 45 | *.su 46 | *.idb 47 | *.pdb 48 | 49 | # Kernel Module Compile Results 50 | *.mod* 51 | *.cmd 52 | .tmp_versions/ 53 | modules.order 54 | Module.symvers 55 | Mkfile.old 56 | dkms.conf 57 | 58 | ### C++ ### 59 | # Prerequisites 60 | 61 | # Compiled Object files 62 | *.slo 63 | 64 | # Precompiled Headers 65 | 66 | # Compiled Dynamic libraries 67 | 68 | # Fortran module files 69 | *.mod 70 | *.smod 71 | 72 | # Compiled Static libraries 73 | *.lai 74 | 75 | # Executables 76 | 77 | ### CMake ### 78 | CMakeLists.txt.user 79 | CMakeCache.txt 80 | CMakeFiles 81 | CMakeScripts 82 | Testing 83 | Makefile 84 | cmake_install.cmake 85 | install_manifest.txt 86 | compile_commands.json 87 | CTestTestfile.cmake 88 | _deps 89 | 90 | ### CMake Patch ### 91 | # External projects 92 | *-prefix/ 93 | 94 | ### Linux ### 95 | *~ 96 | 97 | # temporary files which can be created if a process still has a handle open of a deleted file 98 | .fuse_hidden* 99 | 100 | # KDE directory preferences 101 | .directory 102 | 103 | # Linux trash folder which might appear on any partition or disk 104 | .Trash-* 105 | 106 | # .nfs files are created when an open file is removed but is still being accessed 107 | .nfs* 108 | 109 | ### VisualStudioCode ### 110 | .vscode/* 111 | 112 | 113 | # Local History for Visual Studio Code 114 | .history/ 115 | 116 | # Built Visual Studio Code Extensions 117 | *.vsix 118 | 119 | ### VisualStudioCode Patch ### 120 | # Ignore all local history of files 121 | .history 122 | .ionide 123 | 124 | ### CLion ### 125 | .idea 126 | 127 | ### Windows ### 128 | # Windows thumbnail cache files 129 | Thumbs.db 130 | Thumbs.db:encryptable 131 | ehthumbs.db 132 | ehthumbs_vista.db 133 | 134 | # Dump file 135 | *.stackdump 136 | 137 | # Folder config file 138 | [Dd]esktop.ini 139 | 140 | # Recycle Bin used on file shares 141 | $RECYCLE.BIN/ 142 | 143 | # Windows Installer files 144 | *.cab 145 | *.msi 146 | *.msix 147 | *.msm 148 | *.msp 149 | 150 | # Windows shortcuts 151 | *.lnk 152 | 153 | ### VisualStudio ### 154 | ## Ignore Visual Studio temporary files, build results, and 155 | ## files generated by popular Visual Studio add-ons. 156 | ## 157 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 158 | 159 | # User-specific files 160 | *.rsuser 161 | *.suo 162 | *.user 163 | *.userosscache 164 | *.sln.docstates 165 | 166 | # User-specific files (MonoDevelop/Xamarin Studio) 167 | *.userprefs 168 | 169 | # Mono auto generated files 170 | mono_crash.* 171 | 172 | # Build results 173 | [Dd]ebug/ 174 | [Dd]ebugPublic/ 175 | [Rr]elease/ 176 | [Rr]eleases/ 177 | x64/ 178 | x86/ 179 | [Ww][Ii][Nn]32/ 180 | [Aa][Rr][Mm]/ 181 | [Aa][Rr][Mm]64/ 182 | bld/ 183 | [Bb]in/ 184 | [Oo]bj/ 185 | [Ll]og/ 186 | [Ll]ogs/ 187 | 188 | # Visual Studio 2015/2017 cache/options directory 189 | .vs/ 190 | # Uncomment if you have tasks that create the project's static files in wwwroot 191 | #wwwroot/ 192 | 193 | # Visual Studio 2017 auto generated files 194 | Generated\ Files/ 195 | 196 | # MSTest test Results 197 | [Tt]est[Rr]esult*/ 198 | [Bb]uild[Ll]og.* 199 | 200 | # NUnit 201 | *.VisualState.xml 202 | TestResult.xml 203 | nunit-*.xml 204 | 205 | # Build Results of an ATL Project 206 | [Dd]ebugPS/ 207 | [Rr]eleasePS/ 208 | dlldata.c 209 | 210 | # Benchmark Results 211 | BenchmarkDotNet.Artifacts/ 212 | 213 | # .NET Core 214 | project.lock.json 215 | project.fragment.lock.json 216 | artifacts/ 217 | 218 | # ASP.NET Scaffolding 219 | ScaffoldingReadMe.txt 220 | 221 | # StyleCop 222 | StyleCopReport.xml 223 | 224 | # Files built by Visual Studio 225 | *_i.c 226 | *_p.c 227 | *_h.h 228 | *.meta 229 | *.iobj 230 | *.ipdb 231 | *.pgc 232 | *.pgd 233 | *.rsp 234 | *.sbr 235 | *.tlb 236 | *.tli 237 | *.tlh 238 | *.tmp 239 | *.tmp_proj 240 | *_wpftmp.csproj 241 | *.log 242 | *.tlog 243 | *.vspscc 244 | *.vssscc 245 | .builds 246 | *.pidb 247 | *.svclog 248 | *.scc 249 | 250 | # Chutzpah Test files 251 | _Chutzpah* 252 | 253 | # Visual C++ cache files 254 | ipch/ 255 | *.aps 256 | *.ncb 257 | *.opendb 258 | *.opensdf 259 | *.sdf 260 | *.cachefile 261 | *.VC.db 262 | *.VC.VC.opendb 263 | 264 | # Visual Studio profiler 265 | *.psess 266 | *.vsp 267 | *.vspx 268 | *.sap 269 | 270 | # Visual Studio Trace Files 271 | *.e2e 272 | 273 | # TFS 2012 Local Workspace 274 | $tf/ 275 | 276 | # Guidance Automation Toolkit 277 | *.gpState 278 | 279 | # ReSharper is a .NET coding add-in 280 | _ReSharper*/ 281 | *.[Rr]e[Ss]harper 282 | *.DotSettings.user 283 | 284 | # TeamCity is a build add-in 285 | _TeamCity* 286 | 287 | # DotCover is a Code Coverage Tool 288 | *.dotCover 289 | 290 | # AxoCover is a Code Coverage Tool 291 | .axoCover/* 292 | !.axoCover/settings.json 293 | 294 | # Coverlet is a free, cross platform Code Coverage Tool 295 | coverage*.json 296 | coverage*.xml 297 | coverage*.info 298 | 299 | # Visual Studio code coverage results 300 | *.coverage 301 | *.coveragexml 302 | 303 | # NCrunch 304 | _NCrunch_* 305 | .*crunch*.local.xml 306 | nCrunchTemp_* 307 | 308 | # MightyMoose 309 | *.mm.* 310 | AutoTest.Net/ 311 | 312 | # Web workbench (sass) 313 | .sass-cache/ 314 | 315 | # Installshield output folder 316 | [Ee]xpress/ 317 | 318 | # DocProject is a documentation generator add-in 319 | DocProject/buildhelp/ 320 | DocProject/Help/*.HxT 321 | DocProject/Help/*.HxC 322 | DocProject/Help/*.hhc 323 | DocProject/Help/*.hhk 324 | DocProject/Help/*.hhp 325 | DocProject/Help/Html2 326 | DocProject/Help/html 327 | 328 | # Click-Once directory 329 | publish/ 330 | 331 | # Publish Web Output 332 | *.[Pp]ublish.xml 333 | *.azurePubxml 334 | # Note: Comment the next line if you want to checkin your web deploy settings, 335 | # but database connection strings (with potential passwords) will be unencrypted 336 | *.pubxml 337 | *.publishproj 338 | 339 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 340 | # checkin your Azure Web App publish settings, but sensitive information contained 341 | # in these scripts will be unencrypted 342 | PublishScripts/ 343 | 344 | # NuGet Packages 345 | *.nupkg 346 | # NuGet Symbol Packages 347 | *.snupkg 348 | # The packages folder can be ignored because of Package Restore 349 | **/[Pp]ackages/* 350 | # except build/, which is used as an MSBuild target. 351 | !**/[Pp]ackages/build/ 352 | # Uncomment if necessary however generally it will be regenerated when needed 353 | #!**/[Pp]ackages/repositories.config 354 | # NuGet v3's project.json files produces more ignorable files 355 | *.nuget.props 356 | *.nuget.targets 357 | 358 | # Microsoft Azure Build Output 359 | csx/ 360 | *.build.csdef 361 | 362 | # Microsoft Azure Emulator 363 | ecf/ 364 | rcf/ 365 | 366 | # Windows Store app package directories and files 367 | AppPackages/ 368 | BundleArtifacts/ 369 | Package.StoreAssociation.xml 370 | _pkginfo.txt 371 | *.appx 372 | *.appxbundle 373 | *.appxupload 374 | 375 | # Visual Studio cache files 376 | # files ending in .cache can be ignored 377 | *.[Cc]ache 378 | # but keep track of directories ending in .cache 379 | !?*.[Cc]ache/ 380 | 381 | # Others 382 | ClientBin/ 383 | ~$* 384 | *.dbmdl 385 | *.dbproj.schemaview 386 | *.jfm 387 | *.pfx 388 | *.publishsettings 389 | orleans.codegen.cs 390 | 391 | # Including strong name files can present a security risk 392 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 393 | #*.snk 394 | 395 | # Since there are multiple workflows, uncomment next line to ignore bower_components 396 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 397 | #bower_components/ 398 | 399 | # RIA/Silverlight projects 400 | Generated_Code/ 401 | 402 | # Backup & report files from converting an old project file 403 | # to a newer Visual Studio version. Backup files are not needed, 404 | # because we have git ;-) 405 | _UpgradeReport_Files/ 406 | Backup*/ 407 | UpgradeLog*.XML 408 | UpgradeLog*.htm 409 | ServiceFabricBackup/ 410 | *.rptproj.bak 411 | 412 | # SQL Server files 413 | *.mdf 414 | *.ldf 415 | *.ndf 416 | 417 | # Business Intelligence projects 418 | *.rdl.data 419 | *.bim.layout 420 | *.bim_*.settings 421 | *.rptproj.rsuser 422 | *- [Bb]ackup.rdl 423 | *- [Bb]ackup ([0-9]).rdl 424 | *- [Bb]ackup ([0-9][0-9]).rdl 425 | 426 | # Microsoft Fakes 427 | FakesAssemblies/ 428 | 429 | # GhostDoc plugin setting file 430 | *.GhostDoc.xml 431 | 432 | # Node.js Tools for Visual Studio 433 | .ntvs_analysis.dat 434 | node_modules/ 435 | 436 | # Visual Studio 6 build log 437 | *.plg 438 | 439 | # Visual Studio 6 workspace options file 440 | *.opt 441 | 442 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 443 | *.vbw 444 | 445 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 446 | *.vbp 447 | 448 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 449 | *.dsw 450 | *.dsp 451 | 452 | # Visual Studio 6 technical files 453 | 454 | # Visual Studio LightSwitch build output 455 | **/*.HTMLClient/GeneratedArtifacts 456 | **/*.DesktopClient/GeneratedArtifacts 457 | **/*.DesktopClient/ModelManifest.xml 458 | **/*.Server/GeneratedArtifacts 459 | **/*.Server/ModelManifest.xml 460 | _Pvt_Extensions 461 | 462 | # Paket dependency manager 463 | .paket/paket.exe 464 | paket-files/ 465 | 466 | # FAKE - F# Make 467 | .fake/ 468 | 469 | # CodeRush personal settings 470 | .cr/personal 471 | 472 | # Python Tools for Visual Studio (PTVS) 473 | __pycache__/ 474 | *.pyc 475 | 476 | # Cake - Uncomment if you are using it 477 | # tools/** 478 | # !tools/packages.config 479 | 480 | # Tabs Studio 481 | *.tss 482 | 483 | # Telerik's JustMock configuration file 484 | *.jmconfig 485 | 486 | # BizTalk build output 487 | *.btp.cs 488 | *.btm.cs 489 | *.odx.cs 490 | *.xsd.cs 491 | 492 | # OpenCover UI analysis results 493 | OpenCover/ 494 | 495 | # Azure Stream Analytics local run output 496 | ASALocalRun/ 497 | 498 | # MSBuild Binary and Structured Log 499 | *.binlog 500 | 501 | # NVidia Nsight GPU debugger configuration file 502 | *.nvuser 503 | 504 | # MFractors (Xamarin productivity tool) working folder 505 | .mfractor/ 506 | 507 | # Local History for Visual Studio 508 | .localhistory/ 509 | 510 | # Visual Studio History (VSHistory) files 511 | .vshistory/ 512 | 513 | # BeatPulse healthcheck temp database 514 | healthchecksdb 515 | 516 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 517 | MigrationBackup/ 518 | 519 | # Ionide (cross platform F# VS Code tools) working folder 520 | .ionide/ 521 | 522 | # Fody - auto-generated XML schema 523 | FodyWeavers.xsd 524 | 525 | # VS Code files for those working on multiple tools 526 | *.code-workspace 527 | 528 | # Local History for Visual Studio Code 529 | 530 | # Windows Installer files from build outputs 531 | 532 | # JetBrains Rider 533 | *.sln.iml 534 | 535 | ### VisualStudio Patch ### 536 | # Additional files built by Visual Studio 537 | 538 | # End of https://www.toptal.com/developers/gitignore/api/cmake,c,c++,windows,linux,visualstudio,visualstudiocode 539 | -------------------------------------------------------------------------------- /uart_text_input.c: -------------------------------------------------------------------------------- 1 | #include "uart_text_input.h" 2 | #include 3 | #include "lora_terminal_icons.h" 4 | #include 5 | 6 | struct UART_TextInput { 7 | View* view; 8 | FuriTimer* timer; 9 | }; 10 | 11 | typedef struct { 12 | const char text; 13 | const uint8_t x; 14 | const uint8_t y; 15 | } UART_TextInputKey; 16 | 17 | typedef struct { 18 | const char* header; 19 | char* text_buffer; 20 | size_t text_buffer_size; 21 | bool clear_default_text; 22 | 23 | UART_TextInputCallback callback; 24 | void* callback_context; 25 | 26 | uint8_t selected_row; 27 | uint8_t selected_column; 28 | 29 | UART_TextInputValidatorCallback validator_callback; 30 | void* validator_callback_context; 31 | FuriString* validator_text; 32 | bool valadator_message_visible; 33 | } UART_TextInputModel; 34 | 35 | static const uint8_t keyboard_origin_x = 1; 36 | static const uint8_t keyboard_origin_y = 29; 37 | static const uint8_t keyboard_row_count = 4; 38 | 39 | #define ENTER_KEY '\n' 40 | #define BACKSPACE_KEY '\b' 41 | 42 | static const UART_TextInputKey keyboard_keys_row_1[] = { 43 | {'{', 1, 0}, 44 | {'(', 9, 0}, 45 | {'[', 17, 0}, 46 | {'|', 25, 0}, 47 | {'@', 33, 0}, 48 | {'&', 41, 0}, 49 | {'#', 49, 0}, 50 | {';', 57, 0}, 51 | {'^', 65, 0}, 52 | {'*', 73, 0}, 53 | {'`', 81, 0}, 54 | {'"', 89, 0}, 55 | {'~', 97, 0}, 56 | {'\'', 105, 0}, 57 | {'.', 113, 0}, 58 | {'/', 120, 0}, 59 | }; 60 | 61 | static const UART_TextInputKey keyboard_keys_row_2[] = { 62 | {'Q', 1, 10}, 63 | {'W', 9, 10}, 64 | {'E', 17, 10}, 65 | {'R', 25, 10}, 66 | {'T', 33, 10}, 67 | {'Y', 41, 10}, 68 | {'U', 49, 10}, 69 | {'I', 57, 10}, 70 | {'O', 65, 10}, 71 | {'P', 73, 10}, 72 | {'0', 81, 10}, 73 | {'1', 89, 10}, 74 | {'2', 97, 10}, 75 | {'3', 105, 10}, 76 | {'=', 113, 10}, 77 | {'-', 120, 10}, 78 | }; 79 | 80 | static const UART_TextInputKey keyboard_keys_row_3[] = { 81 | {'A', 1, 21}, 82 | {'S', 9, 21}, 83 | {'D', 18, 21}, 84 | {'F', 25, 21}, 85 | {'G', 33, 21}, 86 | {'H', 41, 21}, 87 | {'J', 49, 21}, 88 | {'K', 57, 21}, 89 | {'L', 65, 21}, 90 | {BACKSPACE_KEY, 72, 13}, 91 | {'4', 89, 21}, 92 | {'5', 97, 21}, 93 | {'6', 105, 21}, 94 | {'$', 113, 21}, 95 | {'%', 120, 21}, 96 | 97 | }; 98 | 99 | static const UART_TextInputKey keyboard_keys_row_4[] = { 100 | {'Z', 1, 33}, 101 | {'X', 9, 33}, 102 | {'C', 18, 33}, 103 | {'V', 25, 33}, 104 | {'B', 33, 33}, 105 | {'N', 41, 33}, 106 | {'M', 49, 33}, 107 | {'_', 57, 33}, 108 | {ENTER_KEY, 64, 24}, 109 | {'7', 89, 33}, 110 | {'8', 97, 33}, 111 | {'9', 105, 33}, 112 | {'!', 113, 33}, 113 | {'+', 120, 33}, 114 | }; 115 | 116 | static uint8_t get_row_size(uint8_t row_index) { 117 | uint8_t row_size = 0; 118 | 119 | switch(row_index + 1) { 120 | case 1: 121 | row_size = sizeof(keyboard_keys_row_1) / sizeof(UART_TextInputKey); 122 | break; 123 | case 2: 124 | row_size = sizeof(keyboard_keys_row_2) / sizeof(UART_TextInputKey); 125 | break; 126 | case 3: 127 | row_size = sizeof(keyboard_keys_row_3) / sizeof(UART_TextInputKey); 128 | break; 129 | case 4: 130 | row_size = sizeof(keyboard_keys_row_4) / sizeof(UART_TextInputKey); 131 | break; 132 | } 133 | 134 | return row_size; 135 | } 136 | 137 | static const UART_TextInputKey* get_row(uint8_t row_index) { 138 | const UART_TextInputKey* row = NULL; 139 | 140 | switch(row_index + 1) { 141 | case 1: 142 | row = keyboard_keys_row_1; 143 | break; 144 | case 2: 145 | row = keyboard_keys_row_2; 146 | break; 147 | case 3: 148 | row = keyboard_keys_row_3; 149 | break; 150 | case 4: 151 | row = keyboard_keys_row_4; 152 | break; 153 | } 154 | 155 | return row; 156 | } 157 | 158 | static char get_selected_char(UART_TextInputModel* model) { 159 | return get_row(model->selected_row)[model->selected_column].text; 160 | } 161 | 162 | static bool char_is_lowercase(char letter) { 163 | return (letter >= 0x61 && letter <= 0x7A); 164 | } 165 | 166 | static char char_to_uppercase(const char letter) { 167 | switch(letter) { 168 | case '_': 169 | return 0x20; 170 | break; 171 | case '(': 172 | return 0x29; 173 | break; 174 | case '{': 175 | return 0x7d; 176 | break; 177 | case '[': 178 | return 0x5d; 179 | break; 180 | case '/': 181 | return 0x5c; 182 | break; 183 | case ';': 184 | return 0x3a; 185 | break; 186 | case '.': 187 | return 0x2c; 188 | break; 189 | case '!': 190 | return 0x3f; 191 | break; 192 | case '<': 193 | return 0x3e; 194 | break; 195 | } 196 | if(isalpha(letter)) { 197 | return (letter - 0x20); 198 | } else { 199 | return letter; 200 | } 201 | } 202 | 203 | static void uart_text_input_backspace_cb(UART_TextInputModel* model) { 204 | uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer); 205 | if(text_length > 0) { 206 | model->text_buffer[text_length - 1] = 0; 207 | } 208 | } 209 | 210 | static void uart_text_input_view_draw_callback(Canvas* canvas, void* _model) { 211 | UART_TextInputModel* model = _model; 212 | uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0; 213 | uint8_t needed_string_width = canvas_width(canvas) - 8; 214 | uint8_t start_pos = 4; 215 | 216 | const char* text = model->text_buffer; 217 | 218 | canvas_clear(canvas); 219 | canvas_set_color(canvas, ColorBlack); 220 | 221 | canvas_draw_str(canvas, 2, 7, model->header); 222 | elements_slightly_rounded_frame(canvas, 1, 8, 126, 12); 223 | 224 | if(canvas_string_width(canvas, text) > needed_string_width) { 225 | canvas_draw_str(canvas, start_pos, 17, "..."); 226 | start_pos += 6; 227 | needed_string_width -= 8; 228 | } 229 | 230 | while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) { 231 | text++; 232 | } 233 | 234 | if(model->clear_default_text) { 235 | elements_slightly_rounded_box( 236 | canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10); 237 | canvas_set_color(canvas, ColorWhite); 238 | } else { 239 | canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 18, "|"); 240 | canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 18, "|"); 241 | } 242 | canvas_draw_str(canvas, start_pos, 17, text); 243 | 244 | canvas_set_font(canvas, FontKeyboard); 245 | 246 | for(uint8_t row = 0; row <= keyboard_row_count; row++) { 247 | const uint8_t column_count = get_row_size(row); 248 | const UART_TextInputKey* keys = get_row(row); 249 | 250 | for(size_t column = 0; column < column_count; column++) { 251 | if(keys[column].text == ENTER_KEY) { 252 | canvas_set_color(canvas, ColorBlack); 253 | if(model->selected_row == row && model->selected_column == column) { 254 | canvas_draw_icon( 255 | canvas, 256 | keyboard_origin_x + keys[column].x, 257 | keyboard_origin_y + keys[column].y, 258 | &I_KeySaveSelected_24x11); 259 | } else { 260 | canvas_draw_icon( 261 | canvas, 262 | keyboard_origin_x + keys[column].x, 263 | keyboard_origin_y + keys[column].y, 264 | &I_KeySave_24x11); 265 | } 266 | } else if(keys[column].text == BACKSPACE_KEY) { 267 | canvas_set_color(canvas, ColorBlack); 268 | if(model->selected_row == row && model->selected_column == column) { 269 | canvas_draw_icon( 270 | canvas, 271 | keyboard_origin_x + keys[column].x, 272 | keyboard_origin_y + keys[column].y, 273 | &I_KeyBackspaceSelected_16x9); 274 | } else { 275 | canvas_draw_icon( 276 | canvas, 277 | keyboard_origin_x + keys[column].x, 278 | keyboard_origin_y + keys[column].y, 279 | &I_KeyBackspace_16x9); 280 | } 281 | } else { 282 | if(model->selected_row == row && model->selected_column == column) { 283 | canvas_set_color(canvas, ColorBlack); 284 | canvas_draw_box( 285 | canvas, 286 | keyboard_origin_x + keys[column].x - 1, 287 | keyboard_origin_y + keys[column].y - 8, 288 | 7, 289 | 10); 290 | canvas_set_color(canvas, ColorWhite); 291 | } else { 292 | canvas_set_color(canvas, ColorBlack); 293 | } 294 | 295 | if(model->clear_default_text || 296 | (text_length == 0 && char_is_lowercase(keys[column].text))) { 297 | canvas_draw_glyph( 298 | canvas, 299 | keyboard_origin_x + keys[column].x, 300 | keyboard_origin_y + keys[column].y, 301 | //char_to_uppercase(keys[column].text)); 302 | keys[column].text); 303 | } else { 304 | canvas_draw_glyph( 305 | canvas, 306 | keyboard_origin_x + keys[column].x, 307 | keyboard_origin_y + keys[column].y, 308 | keys[column].text); 309 | } 310 | } 311 | } 312 | } 313 | if(model->valadator_message_visible) { 314 | canvas_set_font(canvas, FontSecondary); 315 | canvas_set_color(canvas, ColorWhite); 316 | canvas_draw_box(canvas, 8, 10, 110, 48); 317 | canvas_set_color(canvas, ColorBlack); 318 | canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); 319 | canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); 320 | canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); 321 | elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text)); 322 | canvas_set_font(canvas, FontKeyboard); 323 | } 324 | } 325 | 326 | static void 327 | uart_text_input_handle_up(UART_TextInput* uart_text_input, UART_TextInputModel* model) { 328 | UNUSED(uart_text_input); 329 | if(model->selected_row > 0) { 330 | model->selected_row--; 331 | if(model->selected_column > get_row_size(model->selected_row) - 6) { 332 | model->selected_column = model->selected_column + 1; 333 | } 334 | } 335 | } 336 | 337 | static void 338 | uart_text_input_handle_down(UART_TextInput* uart_text_input, UART_TextInputModel* model) { 339 | UNUSED(uart_text_input); 340 | if(model->selected_row < keyboard_row_count - 1) { 341 | model->selected_row++; 342 | if(model->selected_column > get_row_size(model->selected_row) - 4) { 343 | model->selected_column = model->selected_column - 1; 344 | } 345 | } 346 | } 347 | 348 | static void 349 | uart_text_input_handle_left(UART_TextInput* uart_text_input, UART_TextInputModel* model) { 350 | UNUSED(uart_text_input); 351 | if(model->selected_column > 0) { 352 | model->selected_column--; 353 | } else { 354 | model->selected_column = get_row_size(model->selected_row) - 1; 355 | } 356 | } 357 | 358 | static void 359 | uart_text_input_handle_right(UART_TextInput* uart_text_input, UART_TextInputModel* model) { 360 | UNUSED(uart_text_input); 361 | if(model->selected_column < get_row_size(model->selected_row) - 1) { 362 | model->selected_column++; 363 | } else { 364 | model->selected_column = 0; 365 | } 366 | } 367 | 368 | static void uart_text_input_handle_ok( 369 | UART_TextInput* uart_text_input, 370 | UART_TextInputModel* model, 371 | bool shift) { 372 | char selected = get_selected_char(model); 373 | uint8_t text_length = strlen(model->text_buffer); 374 | 375 | if(shift) { 376 | selected = char_to_uppercase(selected); 377 | } 378 | 379 | if(selected == ENTER_KEY) { 380 | if(model->validator_callback && 381 | (!model->validator_callback( 382 | model->text_buffer, model->validator_text, model->validator_callback_context))) { 383 | model->valadator_message_visible = true; 384 | furi_timer_start(uart_text_input->timer, furi_kernel_get_tick_frequency() * 4); 385 | } else if(model->callback != 0 && text_length > 0) { 386 | model->callback(model->callback_context); 387 | } 388 | } else if(selected == BACKSPACE_KEY) { 389 | uart_text_input_backspace_cb(model); 390 | } else { 391 | if(model->clear_default_text) { 392 | text_length = 0; 393 | } 394 | if(text_length < (model->text_buffer_size - 1)) { 395 | if(text_length == 0 && char_is_lowercase(selected)) { 396 | //selected = char_to_uppercase(selected); 397 | } 398 | model->text_buffer[text_length] = selected; 399 | model->text_buffer[text_length + 1] = 0; 400 | } 401 | } 402 | model->clear_default_text = false; 403 | } 404 | 405 | static bool uart_text_input_view_input_callback(InputEvent* event, void* context) { 406 | UART_TextInput* uart_text_input = context; 407 | furi_assert(uart_text_input); 408 | 409 | bool consumed = false; 410 | 411 | // Acquire model 412 | UART_TextInputModel* model = view_get_model(uart_text_input->view); 413 | 414 | if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && 415 | model->valadator_message_visible) { 416 | model->valadator_message_visible = false; 417 | consumed = true; 418 | } else if(event->type == InputTypeShort) { 419 | consumed = true; 420 | switch(event->key) { 421 | case InputKeyUp: 422 | uart_text_input_handle_up(uart_text_input, model); 423 | break; 424 | case InputKeyDown: 425 | uart_text_input_handle_down(uart_text_input, model); 426 | break; 427 | case InputKeyLeft: 428 | uart_text_input_handle_left(uart_text_input, model); 429 | break; 430 | case InputKeyRight: 431 | uart_text_input_handle_right(uart_text_input, model); 432 | break; 433 | case InputKeyOk: 434 | uart_text_input_handle_ok(uart_text_input, model, false); 435 | break; 436 | default: 437 | consumed = false; 438 | break; 439 | } 440 | } else if(event->type == InputTypeLong) { 441 | consumed = true; 442 | switch(event->key) { 443 | case InputKeyUp: 444 | uart_text_input_handle_up(uart_text_input, model); 445 | break; 446 | case InputKeyDown: 447 | uart_text_input_handle_down(uart_text_input, model); 448 | break; 449 | case InputKeyLeft: 450 | uart_text_input_handle_left(uart_text_input, model); 451 | break; 452 | case InputKeyRight: 453 | uart_text_input_handle_right(uart_text_input, model); 454 | break; 455 | case InputKeyOk: 456 | uart_text_input_handle_ok(uart_text_input, model, true); 457 | break; 458 | case InputKeyBack: 459 | uart_text_input_backspace_cb(model); 460 | break; 461 | default: 462 | consumed = false; 463 | break; 464 | } 465 | } else if(event->type == InputTypeRepeat) { 466 | consumed = true; 467 | switch(event->key) { 468 | case InputKeyUp: 469 | uart_text_input_handle_up(uart_text_input, model); 470 | break; 471 | case InputKeyDown: 472 | uart_text_input_handle_down(uart_text_input, model); 473 | break; 474 | case InputKeyLeft: 475 | uart_text_input_handle_left(uart_text_input, model); 476 | break; 477 | case InputKeyRight: 478 | uart_text_input_handle_right(uart_text_input, model); 479 | break; 480 | case InputKeyBack: 481 | uart_text_input_backspace_cb(model); 482 | break; 483 | default: 484 | consumed = false; 485 | break; 486 | } 487 | } 488 | 489 | // Commit model 490 | view_commit_model(uart_text_input->view, consumed); 491 | 492 | return consumed; 493 | } 494 | 495 | void uart_text_input_timer_callback(void* context) { 496 | furi_assert(context); 497 | UART_TextInput* uart_text_input = context; 498 | 499 | with_view_model( 500 | uart_text_input->view, 501 | UART_TextInputModel * model, 502 | { model->valadator_message_visible = false; }, 503 | true); 504 | } 505 | 506 | UART_TextInput* uart_text_input_alloc() { 507 | UART_TextInput* uart_text_input = malloc(sizeof(UART_TextInput)); 508 | uart_text_input->view = view_alloc(); 509 | view_set_context(uart_text_input->view, uart_text_input); 510 | view_allocate_model(uart_text_input->view, ViewModelTypeLocking, sizeof(UART_TextInputModel)); 511 | view_set_draw_callback(uart_text_input->view, uart_text_input_view_draw_callback); 512 | view_set_input_callback(uart_text_input->view, uart_text_input_view_input_callback); 513 | 514 | uart_text_input->timer = 515 | furi_timer_alloc(uart_text_input_timer_callback, FuriTimerTypeOnce, uart_text_input); 516 | 517 | with_view_model( 518 | uart_text_input->view, 519 | UART_TextInputModel * model, 520 | { model->validator_text = furi_string_alloc(); }, 521 | false); 522 | 523 | uart_text_input_reset(uart_text_input); 524 | 525 | return uart_text_input; 526 | } 527 | 528 | void uart_text_input_free(UART_TextInput* uart_text_input) { 529 | furi_assert(uart_text_input); 530 | with_view_model( 531 | uart_text_input->view, 532 | UART_TextInputModel * model, 533 | { furi_string_free(model->validator_text); }, 534 | false); 535 | 536 | // Send stop command 537 | furi_timer_stop(uart_text_input->timer); 538 | // Release allocated memory 539 | furi_timer_free(uart_text_input->timer); 540 | 541 | view_free(uart_text_input->view); 542 | 543 | free(uart_text_input); 544 | } 545 | 546 | void uart_text_input_reset(UART_TextInput* uart_text_input) { 547 | furi_assert(uart_text_input); 548 | with_view_model( 549 | uart_text_input->view, 550 | UART_TextInputModel * model, 551 | { 552 | model->text_buffer_size = 0; 553 | model->header = ""; 554 | model->selected_row = 0; 555 | model->selected_column = 0; 556 | model->clear_default_text = false; 557 | model->text_buffer = NULL; 558 | model->text_buffer_size = 0; 559 | model->callback = NULL; 560 | model->callback_context = NULL; 561 | model->validator_callback = NULL; 562 | model->validator_callback_context = NULL; 563 | furi_string_reset(model->validator_text); 564 | model->valadator_message_visible = false; 565 | }, 566 | true); 567 | } 568 | 569 | View* uart_text_input_get_view(UART_TextInput* uart_text_input) { 570 | furi_assert(uart_text_input); 571 | return uart_text_input->view; 572 | } 573 | 574 | void uart_text_input_set_result_callback( 575 | UART_TextInput* uart_text_input, 576 | UART_TextInputCallback callback, 577 | void* callback_context, 578 | char* text_buffer, 579 | size_t text_buffer_size, 580 | bool clear_default_text) { 581 | with_view_model( 582 | uart_text_input->view, 583 | UART_TextInputModel * model, 584 | { 585 | model->callback = callback; 586 | model->callback_context = callback_context; 587 | model->text_buffer = text_buffer; 588 | model->text_buffer_size = text_buffer_size; 589 | model->clear_default_text = clear_default_text; 590 | if(text_buffer && text_buffer[0] != '\0') { 591 | // Set focus on Save 592 | model->selected_row = 2; 593 | model->selected_column = 8; 594 | } 595 | }, 596 | true); 597 | } 598 | 599 | void uart_text_input_set_validator( 600 | UART_TextInput* uart_text_input, 601 | UART_TextInputValidatorCallback callback, 602 | void* callback_context) { 603 | with_view_model( 604 | uart_text_input->view, 605 | UART_TextInputModel * model, 606 | { 607 | model->validator_callback = callback; 608 | model->validator_callback_context = callback_context; 609 | }, 610 | true); 611 | } 612 | 613 | UART_TextInputValidatorCallback 614 | uart_text_input_get_validator_callback(UART_TextInput* uart_text_input) { 615 | UART_TextInputValidatorCallback validator_callback = NULL; 616 | with_view_model( 617 | uart_text_input->view, 618 | UART_TextInputModel * model, 619 | { validator_callback = model->validator_callback; }, 620 | false); 621 | return validator_callback; 622 | } 623 | 624 | void* uart_text_input_get_validator_callback_context(UART_TextInput* uart_text_input) { 625 | void* validator_callback_context = NULL; 626 | with_view_model( 627 | uart_text_input->view, 628 | UART_TextInputModel * model, 629 | { validator_callback_context = model->validator_callback_context; }, 630 | false); 631 | return validator_callback_context; 632 | } 633 | 634 | void uart_text_input_set_header_text(UART_TextInput* uart_text_input, const char* text) { 635 | with_view_model( 636 | uart_text_input->view, UART_TextInputModel * model, { model->header = text; }, true); 637 | } 638 | --------------------------------------------------------------------------------