From 6c74ea65c27d86a98db55882ff86563bb161423e Mon Sep 17 00:00:00 2001 From: Albert Kharisov Date: Wed, 9 Jun 2021 16:04:49 +0300 Subject: [PATCH] [FL-1369, FL-1397, FL-1420] IRDA + SDcard (#513) * Add saving to SD-Card (not ready yet) * Add saving to SD-card (done) * Select previous menu item * Fix central button * Fix current_button * Refactoring * Add notifications * [FL-1417] Add IRDA CLI CLI commands: 1) ir_rx Receives all IR-trafic, decodes and prints result to stdout 2) ir_tx
Transmits IR-signal. Address and command are hex-formatted * Fix BUG with random memory corruption at random time in random place in random universe in random unknown space and time forever amen * Fix submenu set_selected_item * Bring protocol order back * Add TODO sdcard check --- applications/applications.c | 6 +- applications/gui/elements.c | 8 +- applications/gui/modules/button_menu.c | 18 + applications/gui/modules/button_menu.h | 7 + applications/gui/modules/submenu.c | 28 +- applications/irda/cli/irda-cli.cpp | 104 +++++ applications/irda/irda-app-remote-manager.cpp | 414 ++++++++++++++---- applications/irda/irda-app-remote-manager.hpp | 66 ++- applications/irda/irda-app.cpp | 68 +++ applications/irda/irda-app.hpp | 24 +- .../irda/scene/irda-app-scene-edit-delete.cpp | 22 +- .../scene/irda-app-scene-edit-key-select.cpp | 11 +- .../irda/scene/irda-app-scene-edit-rename.cpp | 19 +- .../irda/scene/irda-app-scene-edit.cpp | 5 + .../scene/irda-app-scene-learn-enter-name.cpp | 13 +- .../scene/irda-app-scene-learn-success.cpp | 22 +- .../irda/scene/irda-app-scene-learn.cpp | 9 + .../irda/scene/irda-app-scene-remote-list.cpp | 26 +- .../irda/scene/irda-app-scene-remote.cpp | 13 +- .../irda/scene/irda-app-scene-start.cpp | 6 + .../irda/scene/irda-app-scene-universal.cpp | 3 + applications/irda/scene/irda-app-scene.hpp | 9 + .../notification-messages-notes.c | 7 +- .../notification-messages-notes.h | 1 + .../notification/notification-messages.c | 20 +- .../notification/notification-messages.h | 2 + .../targets/api-hal-include/api-hal-irda.h | 6 + firmware/targets/f5/api-hal/api-hal-irda.c | 4 + firmware/targets/f6/api-hal/api-hal-irda.c | 4 + lib/irda/irda.c | 62 ++- lib/irda/irda.h | 17 + 31 files changed, 846 insertions(+), 178 deletions(-) create mode 100644 applications/irda/cli/irda-cli.cpp diff --git a/applications/applications.c b/applications/applications.c index 576d32d8..00916956 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -41,6 +41,7 @@ int32_t app_archive(void* p); int32_t notification_app(void* p); // On system start hooks declaration +void irda_cli_init(); void nfc_cli_init(); void subghz_cli_init(); void bt_cli_init(); @@ -104,7 +105,7 @@ const FlipperApplication FLIPPER_SERVICES[] = { #endif #ifdef SRV_IRDA - {.app = irda, .name = "irda", .stack_size = 1024, .icon = A_Plugins_14}, + {.app = irda, .name = "irda", .stack_size = 1024 * 3, .icon = A_Plugins_14}, #endif #ifdef SRV_EXAMPLE_QRCODE @@ -186,7 +187,7 @@ const FlipperApplication FLIPPER_APPS[] = { #endif #ifdef APP_IRDA - {.app = irda, .name = "Infrared", .stack_size = 1024, .icon = A_Infrared_14}, + {.app = irda, .name = "Infrared", .stack_size = 1024 * 3, .icon = A_Infrared_14}, #endif #ifdef APP_GPIO_DEMO @@ -203,6 +204,7 @@ const size_t FLIPPER_APPS_COUNT = sizeof(FLIPPER_APPS) / sizeof(FlipperApplicati // On system start hooks const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { + irda_cli_init, #ifdef APP_NFC nfc_cli_init, #endif diff --git a/applications/gui/elements.c b/applications/gui/elements.c index 64597f53..fd4e5d77 100644 --- a/applications/gui/elements.c +++ b/applications/gui/elements.c @@ -71,7 +71,7 @@ void elements_button_left(Canvas* canvas, const char* str) { const uint8_t horizontal_offset = 3; const uint8_t string_width = canvas_string_width(canvas, str); const IconData* icon = assets_icons_get_data(I_ButtonLeft_4x7); - const uint8_t icon_offset = 6; + const uint8_t icon_offset = 3; const uint8_t icon_width_with_offset = icon->width + icon_offset; const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; @@ -97,7 +97,7 @@ void elements_button_right(Canvas* canvas, const char* str) { const uint8_t horizontal_offset = 3; const uint8_t string_width = canvas_string_width(canvas, str); const IconData* icon = assets_icons_get_data(I_ButtonRight_4x7); - const uint8_t icon_offset = 6; + const uint8_t icon_offset = 3; const uint8_t icon_width_with_offset = icon->width + icon_offset; const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; @@ -122,10 +122,10 @@ void elements_button_right(Canvas* canvas, const char* str) { void elements_button_center(Canvas* canvas, const char* str) { const uint8_t button_height = 13; const uint8_t vertical_offset = 3; - const uint8_t horizontal_offset = 3; + const uint8_t horizontal_offset = 1; const uint8_t string_width = canvas_string_width(canvas, str); const IconData* icon = assets_icons_get_data(I_ButtonCenter_7x7); - const uint8_t icon_offset = 6; + const uint8_t icon_offset = 3; const uint8_t icon_width_with_offset = icon->width + icon_offset; const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; diff --git a/applications/gui/modules/button_menu.c b/applications/gui/modules/button_menu.c index 0545fac1..1576e147 100644 --- a/applications/gui/modules/button_menu.c +++ b/applications/gui/modules/button_menu.c @@ -286,3 +286,21 @@ void button_menu_free(ButtonMenu* button_menu) { view_free(button_menu->view); free(button_menu); } + +void button_menu_set_selected_item(ButtonMenu* button_menu, uint32_t index) { + furi_assert(button_menu); + + with_view_model( + button_menu->view, (ButtonMenuModel * model) { + uint8_t item_position = 0; + ButtonMenuItemArray_it_t it; + for(ButtonMenuItemArray_it(it, model->items); !ButtonMenuItemArray_end_p(it); + ButtonMenuItemArray_next(it), ++item_position) { + if(ButtonMenuItemArray_cref(it)->index == index) { + model->position = item_position; + break; + } + } + return true; + }); +} diff --git a/applications/gui/modules/button_menu.h b/applications/gui/modules/button_menu.h index 5aa25008..8228a95f 100644 --- a/applications/gui/modules/button_menu.h +++ b/applications/gui/modules/button_menu.h @@ -68,6 +68,13 @@ void button_menu_free(ButtonMenu* button_menu); */ void button_menu_set_header(ButtonMenu* button_menu, const char* header); +/** + * @brief Set selected item + * @param button_menu - ButtonMenu instance + * @param index - index of ButtonMenu to be selected + */ +void button_menu_set_selected_item(ButtonMenu* button_menu, uint32_t index); + #ifdef __cplusplus } #endif diff --git a/applications/gui/modules/submenu.c b/applications/gui/modules/submenu.c index d05f3302..c1920662 100644 --- a/applications/gui/modules/submenu.c +++ b/applications/gui/modules/submenu.c @@ -36,12 +36,12 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { const uint8_t item_width = 123; canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); uint8_t position = 0; SubmenuItemArray_it_t it; if(model->header) { + canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 4, 11, model->header); } @@ -49,10 +49,10 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); SubmenuItemArray_next(it)) { uint8_t item_position = position - model->window_position; - uint8_t elements_on_screen = model->header ? 3 : 4; + uint8_t items_on_screen = model->header ? 3 : 4; uint8_t y_offset = model->header ? 16 : 0; - if(item_position < elements_on_screen) { + if(item_position < items_on_screen) { if(position == model->position) { canvas_set_color(canvas, ColorBlack); elements_slightly_rounded_box( @@ -202,11 +202,15 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { model->window_position -= 1; } - if(SubmenuItemArray_size(model->items) <= 4) { + uint8_t items_on_screen = model->header ? 3 : 4; + + if(SubmenuItemArray_size(model->items) <= items_on_screen) { model->window_position = 0; } else { - if(model->window_position >= (SubmenuItemArray_size(model->items) - 4)) { - model->window_position = (SubmenuItemArray_size(model->items) - 4); + if(model->window_position >= + (SubmenuItemArray_size(model->items) - items_on_screen)) { + model->window_position = + (SubmenuItemArray_size(model->items) - items_on_screen); } } @@ -217,7 +221,7 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { void submenu_process_up(Submenu* submenu) { with_view_model( submenu->view, (SubmenuModel * model) { - uint8_t elements_on_screen = model->header ? 3 : 4; + uint8_t items_on_screen = model->header ? 3 : 4; if(model->position > 0) { model->position--; if(((model->position - model->window_position) < 1) && @@ -226,8 +230,8 @@ void submenu_process_up(Submenu* submenu) { } } else { model->position = SubmenuItemArray_size(model->items) - 1; - if(model->position > (elements_on_screen - 1)) { - model->window_position = model->position - (elements_on_screen - 1); + if(model->position > (items_on_screen - 1)) { + model->window_position = model->position - (items_on_screen - 1); } } return true; @@ -237,12 +241,12 @@ void submenu_process_up(Submenu* submenu) { void submenu_process_down(Submenu* submenu) { with_view_model( submenu->view, (SubmenuModel * model) { - uint8_t elements_on_screen = model->header ? 3 : 4; + uint8_t items_on_screen = model->header ? 3 : 4; if(model->position < (SubmenuItemArray_size(model->items) - 1)) { model->position++; - if((model->position - model->window_position) > (elements_on_screen - 2) && + if((model->position - model->window_position) > (items_on_screen - 2) && model->window_position < - (SubmenuItemArray_size(model->items) - elements_on_screen)) { + (SubmenuItemArray_size(model->items) - items_on_screen)) { model->window_position++; } } else { diff --git a/applications/irda/cli/irda-cli.cpp b/applications/irda/cli/irda-cli.cpp new file mode 100644 index 00000000..549e2e23 --- /dev/null +++ b/applications/irda/cli/irda-cli.cpp @@ -0,0 +1,104 @@ +#include "app-template.h" +#include "cli/cli.h" +#include "cmsis_os2.h" +#include +#include +#include "irda.h" +#include +#include +#include + +typedef struct IrdaCli { + IrdaHandler* handler; + osMessageQueueId_t message_queue; +} IrdaCli; + +static void irda_rx_callback(void* ctx, bool level, uint32_t duration) { + IrdaCli* irda_cli = (IrdaCli*)ctx; + const IrdaMessage* message; + message = irda_decode(irda_cli->handler, level, duration); + if(message) { + osMessageQueuePut(irda_cli->message_queue, message, 0, 0); + } +} + +static void irda_cli_start_ir_rx(Cli* cli, string_t args, void* context) { + if(api_hal_irda_rx_irq_is_busy()) { + printf("IRDA is busy. Exit."); + return; + } + IrdaCli irda_cli; + irda_cli.handler = irda_alloc_decoder(); + irda_cli.message_queue = osMessageQueueNew(2, sizeof(IrdaMessage), NULL); + api_hal_irda_rx_irq_init(); + api_hal_irda_rx_irq_set_callback(irda_rx_callback, &irda_cli); + + printf("Receiving IRDA...\r\nPress Ctrl+C to abort\r\n"); + while(!cli_cmd_interrupt_received(cli)) { + IrdaMessage message; + if(osOK == osMessageQueueGet(irda_cli.message_queue, &message, NULL, 50)) { + printf( + "%s, A:0x%0*lX, C:0x%0*lX%s\r\n", + irda_get_protocol_name(message.protocol), + irda_get_protocol_address_length(message.protocol), + message.address, + irda_get_protocol_command_length(message.protocol), + message.command, + message.repeat ? " R" : ""); + } + } + + api_hal_irda_rx_irq_deinit(); + irda_free_decoder(irda_cli.handler); + osMessageQueueDelete(irda_cli.message_queue); +} + +static void irda_cli_print_usage(void) { + printf("Usage:\r\n\tir_tx
\r\n"); + printf("\t and
are hex-formatted\r\n"); + printf("\tAvailable protocols:"); + for(int i = 0; irda_is_protocol_valid((IrdaProtocol)i); ++i) { + printf(" %s", irda_get_protocol_name((IrdaProtocol)i)); + } + printf("\r\n"); +} + +static void irda_cli_start_ir_tx(Cli* cli, string_t args, void* context) { + if(api_hal_irda_rx_irq_is_busy()) { + printf("IRDA is busy. Exit."); + return; + } + auto ss = std::istringstream(string_get_cstr(args)); + uint32_t command = 0; + uint32_t address = 0; + std::string protocol_name; + + if(!(ss >> protocol_name) || !(ss >> std::hex >> address) || !(ss >> std::hex >> command)) { + printf("Wrong arguments.\r\n"); + irda_cli_print_usage(); + return; + } + + IrdaProtocol protocol = irda_get_protocol_by_name(protocol_name.c_str()); + + if(!irda_is_protocol_valid(protocol)) { + printf("Unknown protocol.\r\n"); + irda_cli_print_usage(); + return; + } + + IrdaMessage message = { + .protocol = protocol, + .address = address, + .command = command, + .repeat = false, + }; + irda_send(&message, 1); +} + +extern "C" void irda_cli_init() { + Cli* cli = (Cli*)furi_record_open("cli"); + cli_add_command(cli, "ir_rx", irda_cli_start_ir_rx, NULL); + cli_add_command(cli, "ir_tx", irda_cli_start_ir_tx, NULL); + furi_record_close("cli"); +} diff --git a/applications/irda/irda-app-remote-manager.cpp b/applications/irda/irda-app-remote-manager.cpp index 7e68b4c9..33d763bc 100644 --- a/applications/irda/irda-app-remote-manager.cpp +++ b/applications/irda/irda-app-remote-manager.cpp @@ -1,51 +1,78 @@ #include "irda-app-remote-manager.hpp" +#include "filesystem-api.h" #include "furi.h" +#include "furi/check.h" +#include "gui/modules/button_menu.h" +#include "irda.h" +#include "sys/_stdint.h" +#include #include #include -IrdaAppRemoteManager::IrdaAppRemoteManager() { - // Read from api-hal-storage, and fill remotes -} - +const char* IrdaAppRemoteManager::irda_directory = "irda"; +const char* IrdaAppRemoteManager::irda_extension = ".ir"; static const std::string default_remote_name = "remote"; -void IrdaAppRemoteManager::add_button(const char* button_name, const IrdaMessage* message) { - remotes[current_remote_index].buttons.emplace_back(button_name, message); +static bool find_string(const std::vector& strings, const std::string& match_string) { + for(const auto& string : strings) { + if(!string.compare(match_string)) return true; + } + return false; } -void IrdaAppRemoteManager::add_remote_with_button( +static std::string +find_vacant_name(const std::vector& strings, const std::string& name) { + // if suggested name is occupied, try another one (name2, name3, etc) + if(find_string(strings, name)) { + int i = 1; + while(find_string(strings, name + std::to_string(++i))) + ; + return name + std::to_string(i); + } else { + return name; + } +} + +IrdaAppRemoteManager::IrdaAppRemoteManager() { + sd_ex_api = static_cast(furi_record_open("sdcard-ex")); + fs_api = static_cast(furi_record_open("sdcard")); +} + +IrdaAppRemoteManager::~IrdaAppRemoteManager() { + furi_record_close("sdcard"); + furi_record_close("sdcard-ex"); +} + +bool IrdaAppRemoteManager::add_button(const char* button_name, const IrdaMessage* message) { + remote->buttons.emplace_back(button_name, message); + return store(); +} + +bool IrdaAppRemoteManager::add_remote_with_button( const char* button_name, const IrdaMessage* message) { - bool found = true; - int i = 0; + furi_check(button_name != nullptr); + furi_check(message != nullptr); - // find first free common name for remote - do { - found = false; - ++i; - for(const auto& it : remotes) { - if(it.name == (default_remote_name + std::to_string(i))) { - found = true; - break; - } - } - } while(found); + std::vector remote_list; + bool result = get_remote_list(remote_list); + if(!result) return false; - remotes.emplace_back(default_remote_name + std::to_string(i)); - current_remote_index = remotes.size() - 1; - add_button(button_name, message); + auto new_name = find_vacant_name(remote_list, default_remote_name); + + remote = std::make_unique(new_name); + return add_button(button_name, message); } -IrdaAppRemote::IrdaAppRemote(std::string name) +IrdaAppRemote::IrdaAppRemote(const std::string& name) : name(name) { } std::vector IrdaAppRemoteManager::get_button_list(void) const { std::vector name_vector; - auto remote = remotes[current_remote_index]; - name_vector.reserve(remote.buttons.size()); + name_vector.reserve(remote->buttons.size()); - for(const auto& it : remote.buttons) { + for(const auto& it : remote->buttons) { name_vector.emplace_back(it.name); } @@ -53,78 +80,289 @@ std::vector IrdaAppRemoteManager::get_button_list(void) const { return name_vector; } -std::vector IrdaAppRemoteManager::get_remote_list() const { - std::vector name_vector; - name_vector.reserve(remotes.size()); +const IrdaMessage* IrdaAppRemoteManager::get_button_data(size_t index) const { + furi_check(remote.get() != nullptr); + auto& buttons = remote->buttons; + furi_check(index < buttons.size()); - for(const auto& it : remotes) { - name_vector.push_back(it.name); + return &buttons.at(index).message; +} + +std::string IrdaAppRemoteManager::make_filename(const std::string& name) const { + return std::string("/") + irda_directory + "/" + name + irda_extension; +} + +bool IrdaAppRemoteManager::delete_remote() { + FS_Error fs_res; + + fs_res = fs_api->common.remove(make_filename(remote->name).c_str()); + if(fs_res != FSE_OK) { + show_file_error_message("Error deleting file"); + return false; + } + remote.reset(); + return true; +} + +bool IrdaAppRemoteManager::delete_button(uint32_t index) { + furi_check(remote.get() != nullptr); + auto& buttons = remote->buttons; + furi_check(index < buttons.size()); + + buttons.erase(buttons.begin() + index); + return store(); +} + +std::string IrdaAppRemoteManager::get_button_name(uint32_t index) { + furi_check(remote.get() != nullptr); + auto& buttons = remote->buttons; + furi_check(index < buttons.size()); + return buttons[index].name; +} + +std::string IrdaAppRemoteManager::get_remote_name() { + furi_check(remote.get() != nullptr); + return remote->name; +} + +int IrdaAppRemoteManager::find_remote_name(const std::vector& strings) { + int i = 0; + for(const auto& str : strings) { + if(!str.compare(remote->name)) { + return i; + } + ++i; + } + return -1; +} + +bool IrdaAppRemoteManager::rename_remote(const char* str) { + furi_check(str != nullptr); + furi_check(remote.get() != nullptr); + + if(!remote->name.compare(str)) return true; + + std::vector remote_list; + bool result = get_remote_list(remote_list); + if(!result) return false; + + auto new_name = find_vacant_name(remote_list, str); + FS_Error fs_err = fs_api->common.rename( + make_filename(remote->name).c_str(), make_filename(new_name).c_str()); + remote->name = new_name; + if(fs_err != FSE_OK) { + show_file_error_message("Error renaming\nremote file"); + } + return fs_err == FSE_OK; +} + +bool IrdaAppRemoteManager::rename_button(uint32_t index, const char* str) { + furi_check(remote.get() != nullptr); + auto& buttons = remote->buttons; + furi_check(index < buttons.size()); + + buttons[index].name = str; + return store(); +} + +size_t IrdaAppRemoteManager::get_number_of_buttons() { + furi_check(remote.get() != nullptr); + return remote->buttons.size(); +} + +void IrdaAppRemoteManager::show_file_error_message(const char* error_text) const { + sd_ex_api->show_error(sd_ex_api->context, error_text); +} + +bool IrdaAppRemoteManager::store(void) { + File file; + uint16_t write_count; + std::string dirname(std::string("/") + irda_directory); + + FS_Error fs_err = fs_api->common.mkdir(dirname.c_str()); + if((fs_err != FSE_OK) && (fs_err != FSE_EXIST)) { + show_file_error_message("Can't create directory"); + return false; } - // copy elision - return name_vector; + std::string filename = dirname + "/" + remote->name + irda_extension; + bool res = fs_api->file.open(&file, filename.c_str(), FSAM_WRITE, FSOM_CREATE_ALWAYS); + + if(!res) { + show_file_error_message("Cannot create\nnew remote file"); + return false; + } + + char content[128]; + + for(const auto& button : remote->buttons) { + auto protocol = button.message.protocol; + + sniprintf( + content, + sizeof(content), + "%.31s %.31s A:%0*lX C:%0*lX\n", + button.name.c_str(), + irda_get_protocol_name(protocol), + irda_get_protocol_address_length(protocol), + button.message.address, + irda_get_protocol_command_length(protocol), + button.message.command); + + auto content_len = strlen(content); + write_count = fs_api->file.write(&file, content, content_len); + if(file.error_id != FSE_OK || write_count != content_len) { + show_file_error_message("Cannot write\nto key file"); + fs_api->file.close(&file); + return false; + } + } + + fs_api->file.close(&file); + sd_ex_api->check_error(sd_ex_api->context); + + return true; } -size_t IrdaAppRemoteManager::get_current_remote(void) const { - return current_remote_index; +bool IrdaAppRemoteManager::parse_button(std::string& str) { + char button_name[32]; + char protocol_name[32]; + uint32_t address; + uint32_t command; + + int parsed = std::sscanf( + str.c_str(), "%31s %31s A:%lX C:%lX", button_name, protocol_name, &address, &command); + + if(parsed != 4) { + return false; + } + + IrdaProtocol protocol = irda_get_protocol_by_name(protocol_name); + + if(!irda_is_protocol_valid((IrdaProtocol)protocol)) { + return false; + } + + int address_length = irda_get_protocol_address_length(protocol); + uint32_t address_mask = (1LU << (4 * address_length)) - 1; + if(address != (address & address_mask)) { + return false; + } + + int command_length = irda_get_protocol_command_length(protocol); + uint32_t command_mask = (1LU << (4 * command_length)) - 1; + if(command != (command & command_mask)) { + return false; + } + + IrdaMessage irda_message = { + .protocol = protocol, + .address = address, + .command = command, + .repeat = false, + }; + remote->buttons.emplace_back(button_name, &irda_message); + + return true; } -size_t IrdaAppRemoteManager::get_current_button(void) const { - return current_button_index; +std::string getline( + const FS_Api* fs_api, + File& file, + char file_buf[], + size_t file_buf_size, + size_t& file_buf_cnt) { + std::string str; + size_t newline_index = 0; + bool found_eol = false; + + while(1) { + if(file_buf_cnt > 0) { + size_t end_index = 0; + char* endline_ptr = (char*)memchr(file_buf, '\n', file_buf_cnt); + newline_index = endline_ptr - file_buf; + + if(endline_ptr == 0) { + end_index = file_buf_cnt; + } else if(newline_index < file_buf_cnt) { + end_index = newline_index + 1; + found_eol = true; + } else { + furi_assert(0); + } + + str.append(file_buf, end_index); + memmove(file_buf, &file_buf[end_index], file_buf_cnt - end_index); + file_buf_cnt = file_buf_cnt - end_index; + if(found_eol) break; + } + + file_buf_cnt += + fs_api->file.read(&file, &file_buf[file_buf_cnt], file_buf_size - file_buf_cnt); + if(file_buf_cnt == 0) { + break; // end of reading + } + } + + return str; } -void IrdaAppRemote::add_button( - size_t remote_index, - const char* button_name, - const IrdaMessage* message) { - buttons.emplace_back(button_name, message); +bool IrdaAppRemoteManager::get_remote_list(std::vector& remote_names) const { + bool fs_res = false; + char name[128]; + File dir; + std::string dirname(std::string("/") + irda_directory); + remote_names.clear(); + + fs_res = fs_api->dir.open(&dir, dirname.c_str()); + if(!fs_res) { + if(!check_fs()) { + show_file_error_message("Cannot open\napplication directory"); + return false; + } else { + return true; // SD ok, but no files written yet + } + } + + while(fs_api->dir.read(&dir, nullptr, name, sizeof(name)) && strlen(name)) { + std::string filename(name); + auto extension_index = filename.rfind(irda_extension); + if((extension_index == std::string::npos) || + (extension_index + strlen(irda_extension) != filename.size())) { + continue; + } + remote_names.push_back(filename.erase(extension_index)); + } + fs_api->dir.close(&dir); + + return true; } -const IrdaMessage* IrdaAppRemoteManager::get_button_data(size_t button_index) const { - furi_check(remotes[current_remote_index].buttons.size() > button_index); - auto& b = remotes[current_remote_index].buttons.at(button_index); - return &b.message; +bool IrdaAppRemoteManager::load(const std::string& name) { + bool fs_res = false; + File file; + + fs_res = fs_api->file.open(&file, make_filename(name).c_str(), FSAM_READ, FSOM_OPEN_EXISTING); + if(!fs_res) { + show_file_error_message("Error opening file"); + return false; + } + + remote = std::make_unique(name); + + while(1) { + auto str = getline(fs_api, file, file_buf, sizeof(file_buf), file_buf_cnt); + if(str.empty()) break; + parse_button(str); + } + fs_api->file.close(&file); + + return true; } -void IrdaAppRemoteManager::set_current_remote(size_t index) { - furi_check(index < remotes.size()); - current_remote_index = index; -} - -void IrdaAppRemoteManager::set_current_button(size_t index) { - furi_check(current_remote_index < remotes.size()); - furi_check(index < remotes[current_remote_index].buttons.size()); - current_button_index = index; -} - -void IrdaAppRemoteManager::delete_current_remote() { - remotes.erase(remotes.begin() + current_remote_index); - current_remote_index = 0; -} - -void IrdaAppRemoteManager::delete_current_button() { - auto& buttons = remotes[current_remote_index].buttons; - buttons.erase(buttons.begin() + current_button_index); - current_button_index = 0; -} - -std::string IrdaAppRemoteManager::get_current_button_name() { - auto buttons = remotes[current_remote_index].buttons; - return buttons[current_button_index].name; -} - -std::string IrdaAppRemoteManager::get_current_remote_name() { - return remotes[current_remote_index].name; -} - -void IrdaAppRemoteManager::rename_remote(const char* str) { - remotes[current_remote_index].name = str; -} - -void IrdaAppRemoteManager::rename_button(const char* str) { - remotes[current_remote_index].buttons[current_button_index].name = str; -} - -size_t IrdaAppRemoteManager::get_current_remote_buttons_number() { - return remotes[current_remote_index].buttons.size(); +bool IrdaAppRemoteManager::check_fs() const { + // TODO: [FL-1431] Add return value to sd_ex_api->check_error() and replace get_fs_info(). + auto fs_err = fs_api->common.get_fs_info(nullptr, nullptr); + if(fs_err != FSE_OK) show_file_error_message("SD card not found"); + return fs_err == FSE_OK; } diff --git a/applications/irda/irda-app-remote-manager.hpp b/applications/irda/irda-app-remote-manager.hpp index 1930ba5b..dd0ae9bc 100644 --- a/applications/irda/irda-app-remote-manager.hpp +++ b/applications/irda/irda-app-remote-manager.hpp @@ -1,9 +1,14 @@ #pragma once +#include "sys/_stdint.h" +#include #include #include #include #include +#include #include +#include +#include class IrdaAppRemoteButton { friend class IrdaAppRemoteManager; @@ -19,35 +24,50 @@ class IrdaAppRemote { friend class IrdaAppRemoteManager; std::vector buttons; std::string name; - bool add(const IrdaMessage*); - void add_button(size_t remote_index, const char* button_name, const IrdaMessage* message); public: - IrdaAppRemote(std::string name); + IrdaAppRemote(const std::string& name); + IrdaAppRemote& operator=(std::string& new_name) noexcept + { + name = new_name; + buttons.clear(); + return *this; + } }; class IrdaAppRemoteManager { - size_t current_remote_index; - size_t current_button_index; - std::vector remotes; -public: - std::vector get_remote_list() const; - std::vector get_button_list() const; - void add_remote_with_button(const char* button_name, const IrdaMessage* message); - void add_button(const char* button_name, const IrdaMessage* message); + static const char* irda_directory; + static const char* irda_extension; + std::unique_ptr remote; + // TODO: make FS_Api and SdCard_Api unique_ptr + SdCard_Api* sd_ex_api; + FS_Api* fs_api; + void show_file_error_message(const char* error_text) const; + bool parse_button(std::string& str); + std::string make_filename(const std::string& name) const; + char file_buf[48]; + size_t file_buf_cnt = 0; - size_t get_current_remote(void) const; - size_t get_current_button(void) const; +public: + bool add_remote_with_button(const char* button_name, const IrdaMessage* message); + bool add_button(const char* button_name, const IrdaMessage* message); + + int find_remote_name(const std::vector& strings); + bool rename_button(uint32_t index, const char* str); + bool rename_remote(const char* str); + + bool get_remote_list(std::vector& remote_names) const; + std::vector get_button_list() const; + std::string get_button_name(uint32_t index); + std::string get_remote_name(); + size_t get_number_of_buttons(); const IrdaMessage* get_button_data(size_t button_index) const; - void set_current_remote(size_t index); - void set_current_button(size_t index); - void rename_button(const char* str); - void rename_remote(const char* str); - std::string get_current_button_name(); - std::string get_current_remote_name(); - size_t get_current_remote_buttons_number(); - void delete_current_button(); - void delete_current_remote(); + bool delete_button(uint32_t index); + bool delete_remote(); IrdaAppRemoteManager(); - ~IrdaAppRemoteManager() {}; + ~IrdaAppRemoteManager(); + + bool store(); + bool load(const std::string& name); + bool check_fs() const; }; diff --git a/applications/irda/irda-app.cpp b/applications/irda/irda-app.cpp index a4d69403..95b9a137 100644 --- a/applications/irda/irda-app.cpp +++ b/applications/irda/irda-app.cpp @@ -1,4 +1,5 @@ #include "irda-app.hpp" +#include "sys/_stdint.h" #include #include #include @@ -154,3 +155,70 @@ void IrdaApp::set_edit_action(IrdaApp::EditAction value) { IrdaApp::EditAction IrdaApp::get_edit_action(void) { return action; } + +void IrdaApp::set_current_button(int value) { + current_button = value; +} + +int IrdaApp::get_current_button() { + return current_button; +} + +void IrdaApp::notify_success() { + notification_message(notification, &sequence_success); +} + +void IrdaApp::notify_red_blink() { + notification_message(notification, &sequence_blink_red_10); +} + +void IrdaApp::notify_space_blink() { + static const NotificationSequence sequence = { + &message_green_0, + &message_delay_50, + &message_green_255, + &message_do_not_reset, + NULL, + }; + + notification_message_block(notification, &sequence); +} + +void IrdaApp::notify_click() { + static const NotificationSequence sequence = { + &message_click, + &message_delay_1, + &message_sound_off, + NULL, + }; + + notification_message_block(notification, &sequence); +} + +void IrdaApp::notify_click_and_blink() { + static const NotificationSequence sequence = { + &message_click, + &message_delay_1, + &message_sound_off, + &message_red_0, + &message_green_255, + &message_blue_0, + &message_delay_10, + &message_green_0, + NULL, + }; + + notification_message_block(notification, &sequence); +} + +void IrdaApp::notify_double_vibro() { + notification_message(notification, &sequence_double_vibro); +} + +void IrdaApp::notify_green_on() { + notification_message(notification, &sequence_set_only_green_255); +} + +void IrdaApp::notify_green_off() { + notification_message(notification, &sequence_reset_green); +} diff --git a/applications/irda/irda-app.hpp b/applications/irda/irda-app.hpp index c1e8ce62..16ba3f49 100644 --- a/applications/irda/irda-app.hpp +++ b/applications/irda/irda-app.hpp @@ -1,4 +1,5 @@ #pragma once +#include "sys/_stdint.h" #include #include #include @@ -9,6 +10,7 @@ #include "irda-app-receiver.hpp" #include #include +#include class IrdaApp { @@ -65,11 +67,29 @@ public: bool get_learn_new_remote(); void set_learn_new_remote(bool value); + enum : int { + ButtonNA = -1, + }; + int get_current_button(); + void set_current_button(int value); + + void notify_success(); + void notify_red_blink(); + void notify_space_blink(); + void notify_double_vibro(); + void notify_green_on(); + void notify_green_off(); + void notify_click(); + void notify_click_and_blink(); + static void text_input_callback(void* context, char* text); static void popup_callback(void* context); - IrdaApp() {} + IrdaApp() { + notification = static_cast(furi_record_open("notification")); + } ~IrdaApp() { + furi_record_close("notification"); for (auto &it : scenes) delete it.second; } @@ -80,7 +100,9 @@ private: bool learn_new_remote; EditElement element; EditAction action; + uint32_t current_button; + NotificationApp* notification; IrdaAppSignalReceiver receiver; IrdaAppViewManager view_manager; IrdaAppRemoteManager remote_manager; diff --git a/applications/irda/scene/irda-app-scene-edit-delete.cpp b/applications/irda/scene/irda-app-scene-edit-delete.cpp index 929dd2d4..a2d8934f 100644 --- a/applications/irda/scene/irda-app-scene-edit-delete.cpp +++ b/applications/irda/scene/irda-app-scene-edit-delete.cpp @@ -1,5 +1,6 @@ #include "../irda-app.hpp" #include "irda.h" +#include "irda/scene/irda-app-scene.hpp" #include #include @@ -20,12 +21,12 @@ void IrdaAppSceneEditDelete::on_enter(IrdaApp* app) { auto remote_manager = app->get_remote_manager(); if(app->get_edit_element() == IrdaApp::EditElement::Button) { - auto message = remote_manager->get_button_data(remote_manager->get_current_button()); + auto message = remote_manager->get_button_data(app->get_current_button()); dialog_ex_set_header(dialog_ex, "Delete button?", 64, 6, AlignCenter, AlignCenter); app->set_text_store( 0, "%s\n%s\nA=0x%0*lX C=0x%0*lX", - remote_manager->get_current_button_name().c_str(), + remote_manager->get_button_name(app->get_current_button()).c_str(), irda_get_protocol_name(message->protocol), irda_get_protocol_address_length(message->protocol), message->address, @@ -36,8 +37,8 @@ void IrdaAppSceneEditDelete::on_enter(IrdaApp* app) { app->set_text_store( 0, "%s\n with %lu buttons", - remote_manager->get_current_remote_name().c_str(), - remote_manager->get_current_remote_buttons_number()); + remote_manager->get_remote_name().c_str(), + remote_manager->get_number_of_buttons()); } dialog_ex_set_text(dialog_ex, app->get_text_store(0), 64, 32, AlignCenter, AlignCenter); @@ -63,13 +64,20 @@ bool IrdaAppSceneEditDelete::on_event(IrdaApp* app, IrdaAppEvent* event) { break; case DialogExResultRight: auto remote_manager = app->get_remote_manager(); + bool result = false; if(app->get_edit_element() == IrdaApp::EditElement::Remote) { - remote_manager->delete_current_remote(); + result = remote_manager->delete_remote(); } else { - remote_manager->delete_current_button(); + result = remote_manager->delete_button(app->get_current_button()); + app->set_current_button(IrdaApp::ButtonNA); } - app->switch_to_next_scene(IrdaApp::Scene::EditDeleteDone); + if(!result) { + app->search_and_switch_to_previous_scene( + {IrdaApp::Scene::RemoteList, IrdaApp::Scene::Start}); + } else { + app->switch_to_next_scene(IrdaApp::Scene::EditDeleteDone); + } break; } } diff --git a/applications/irda/scene/irda-app-scene-edit-key-select.cpp b/applications/irda/scene/irda-app-scene-edit-key-select.cpp index e0ead609..41956b1d 100644 --- a/applications/irda/scene/irda-app-scene-edit-key-select.cpp +++ b/applications/irda/scene/irda-app-scene-edit-key-select.cpp @@ -14,7 +14,7 @@ static void submenu_callback(void* context, uint32_t index) { void IrdaAppSceneEditKeySelect::on_enter(IrdaApp* app) { IrdaAppViewManager* view_manager = app->get_view_manager(); Submenu* submenu = view_manager->get_submenu(); - int i = 0; + int item_number = 0; const char* header = app->get_edit_action() == IrdaApp::EditAction::Rename ? "Rename key:" : "Delete key:"; @@ -23,7 +23,11 @@ void IrdaAppSceneEditKeySelect::on_enter(IrdaApp* app) { auto remote_manager = app->get_remote_manager(); buttons_names = remote_manager->get_button_list(); for(const auto& it : buttons_names) { - submenu_add_item(submenu, it.c_str(), i++, submenu_callback, app); + submenu_add_item(submenu, it.c_str(), item_number++, submenu_callback, app); + } + if((item_number > 0) && (app->get_current_button() != IrdaApp::ButtonNA)) { + submenu_set_selected_item(submenu, app->get_current_button()); + app->set_current_button(IrdaApp::ButtonNA); } view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); @@ -33,8 +37,7 @@ bool IrdaAppSceneEditKeySelect::on_event(IrdaApp* app, IrdaAppEvent* event) { bool consumed = false; if(event->type == IrdaAppEvent::Type::MenuSelected) { - auto remote_manager = app->get_remote_manager(); - remote_manager->set_current_button(event->payload.menu_index); + app->set_current_button(event->payload.menu_index); consumed = true; if(app->get_edit_action() == IrdaApp::EditAction::Rename) { app->switch_to_next_scene(IrdaApp::Scene::EditRename); diff --git a/applications/irda/scene/irda-app-scene-edit-rename.cpp b/applications/irda/scene/irda-app-scene-edit-rename.cpp index 0ade41ca..0ed3700e 100644 --- a/applications/irda/scene/irda-app-scene-edit-rename.cpp +++ b/applications/irda/scene/irda-app-scene-edit-rename.cpp @@ -7,10 +7,11 @@ void IrdaAppSceneEditRename::on_enter(IrdaApp* app) { auto remote_manager = app->get_remote_manager(); if(app->get_edit_element() == IrdaApp::EditElement::Button) { - auto button_name = remote_manager->get_current_button_name(); + furi_assert(app->get_current_button() != IrdaApp::ButtonNA); + auto button_name = remote_manager->get_button_name(app->get_current_button()); strncpy(app->get_text_store(0), button_name.c_str(), app->get_text_store_size()); } else { - auto remote_name = remote_manager->get_current_remote_name(); + auto remote_name = remote_manager->get_remote_name(); strncpy(app->get_text_store(0), remote_name.c_str(), app->get_text_store_size()); } @@ -30,12 +31,20 @@ bool IrdaAppSceneEditRename::on_event(IrdaApp* app, IrdaAppEvent* event) { if(event->type == IrdaAppEvent::Type::TextEditDone) { auto remote_manager = app->get_remote_manager(); + bool result = false; if(app->get_edit_element() == IrdaApp::EditElement::Button) { - remote_manager->rename_button(app->get_text_store(0)); + result = + remote_manager->rename_button(app->get_current_button(), app->get_text_store(0)); + app->set_current_button(IrdaApp::ButtonNA); } else { - remote_manager->rename_remote(app->get_text_store(0)); + result = remote_manager->rename_remote(app->get_text_store(0)); + } + if(!result) { + app->search_and_switch_to_previous_scene( + {IrdaApp::Scene::Start, IrdaApp::Scene::RemoteList}); + } else { + app->switch_to_next_scene_without_saving(IrdaApp::Scene::EditRenameDone); } - app->switch_to_next_scene_without_saving(IrdaApp::Scene::EditRenameDone); consumed = true; } diff --git a/applications/irda/scene/irda-app-scene-edit.cpp b/applications/irda/scene/irda-app-scene-edit.cpp index cd8a84fc..0ecb8d9e 100644 --- a/applications/irda/scene/irda-app-scene-edit.cpp +++ b/applications/irda/scene/irda-app-scene-edit.cpp @@ -1,4 +1,5 @@ #include "../irda-app.hpp" +#include "gui/modules/submenu.h" typedef enum { SubmenuIndexAddKey, @@ -27,6 +28,8 @@ void IrdaAppSceneEdit::on_enter(IrdaApp* app) { submenu_add_item(submenu, "Delete key", SubmenuIndexDeleteKey, submenu_callback, app); submenu_add_item(submenu, "Rename remote", SubmenuIndexRenameRemote, submenu_callback, app); submenu_add_item(submenu, "Delete remote", SubmenuIndexDeleteRemote, submenu_callback, app); + submenu_set_selected_item(submenu, submenu_item_selected); + submenu_item_selected = 0; view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); } @@ -35,8 +38,10 @@ bool IrdaAppSceneEdit::on_event(IrdaApp* app, IrdaAppEvent* event) { bool consumed = false; if(event->type == IrdaAppEvent::Type::MenuSelected) { + submenu_item_selected = event->payload.menu_index; switch(event->payload.menu_index) { case SubmenuIndexAddKey: + app->set_learn_new_remote(false); app->switch_to_next_scene(IrdaApp::Scene::Learn); break; case SubmenuIndexRenameKey: diff --git a/applications/irda/scene/irda-app-scene-learn-enter-name.cpp b/applications/irda/scene/irda-app-scene-learn-enter-name.cpp index 49b581d1..590902ff 100644 --- a/applications/irda/scene/irda-app-scene-learn-enter-name.cpp +++ b/applications/irda/scene/irda-app-scene-learn-enter-name.cpp @@ -35,14 +35,21 @@ bool IrdaAppSceneLearnEnterName::on_event(IrdaApp* app, IrdaAppEvent* event) { if(event->type == IrdaAppEvent::Type::TextEditDone) { auto remote_manager = app->get_remote_manager(); auto receiver = app->get_receiver(); + bool result = false; if(app->get_learn_new_remote()) { - remote_manager->add_remote_with_button( + result = remote_manager->add_remote_with_button( app->get_text_store(0), receiver->get_last_message()); } else { - remote_manager->add_button(app->get_text_store(0), receiver->get_last_message()); + result = + remote_manager->add_button(app->get_text_store(0), receiver->get_last_message()); } - app->switch_to_next_scene_without_saving(IrdaApp::Scene::LearnDone); + if(!result) { + app->search_and_switch_to_previous_scene( + {IrdaApp::Scene::Start, IrdaApp::Scene::RemoteList}); + } else { + app->switch_to_next_scene_without_saving(IrdaApp::Scene::LearnDone); + } } return consumed; } diff --git a/applications/irda/scene/irda-app-scene-learn-success.cpp b/applications/irda/scene/irda-app-scene-learn-success.cpp index 12a7f9ad..b4037d78 100644 --- a/applications/irda/scene/irda-app-scene-learn-success.cpp +++ b/applications/irda/scene/irda-app-scene-learn-success.cpp @@ -17,6 +17,8 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { IrdaAppViewManager* view_manager = app->get_view_manager(); DialogEx* dialog_ex = view_manager->get_dialog_ex(); + app->notify_green_on(); + auto receiver = app->get_receiver(); auto message = receiver->get_last_message(); @@ -32,6 +34,7 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { dialog_ex_set_text(dialog_ex, app->get_text_store(1), 75, 23, AlignLeft, AlignTop); dialog_ex_set_left_button_text(dialog_ex, "Retry"); dialog_ex_set_right_button_text(dialog_ex, "Save"); + dialog_ex_set_center_button_text(dialog_ex, "Send"); dialog_ex_set_icon(dialog_ex, 0, 1, I_DolphinExcited_64x63); dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); dialog_ex_set_context(dialog_ex, app); @@ -47,11 +50,20 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { case DialogExResultLeft: app->switch_to_next_scene_without_saving(IrdaApp::Scene::Learn); break; - case DialogExResultCenter: - furi_assert(0); + case DialogExResultCenter: { + app->notify_space_blink(); + auto receiver = app->get_receiver(); + auto message = receiver->get_last_message(); + irda_send(message, 1); break; + } case DialogExResultRight: - app->switch_to_next_scene(IrdaApp::Scene::LearnEnterName); + auto remote_manager = app->get_remote_manager(); + if(remote_manager->check_fs()) { + app->switch_to_next_scene(IrdaApp::Scene::LearnEnterName); + } else { + app->switch_to_previous_scene(); + } break; } } @@ -60,4 +72,8 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { } void IrdaAppSceneLearnSuccess::on_exit(IrdaApp* app) { + IrdaAppViewManager* view_manager = app->get_view_manager(); + DialogEx* dialog_ex = view_manager->get_dialog_ex(); + dialog_ex_set_center_button_text(dialog_ex, nullptr); + app->notify_green_off(); } diff --git a/applications/irda/scene/irda-app-scene-learn.cpp b/applications/irda/scene/irda-app-scene-learn.cpp index 993575bb..c551706a 100644 --- a/applications/irda/scene/irda-app-scene-learn.cpp +++ b/applications/irda/scene/irda-app-scene-learn.cpp @@ -14,13 +14,22 @@ void IrdaAppSceneLearn::on_enter(IrdaApp* app) { popup, "Point the remote at IR port\nand push the button", 5, 10, AlignLeft, AlignCenter); popup_set_callback(popup, NULL); + if(app->get_learn_new_remote()) { + app->notify_double_vibro(); + } + view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); } bool IrdaAppSceneLearn::on_event(IrdaApp* app, IrdaAppEvent* event) { bool consumed = false; + if(event->type == IrdaAppEvent::Type::Tick) { + consumed = true; + app->notify_red_blink(); + } if(event->type == IrdaAppEvent::Type::IrdaMessageReceived) { + app->notify_success(); app->switch_to_next_scene_without_saving(IrdaApp::Scene::LearnSuccess); } diff --git a/applications/irda/scene/irda-app-scene-remote-list.cpp b/applications/irda/scene/irda-app-scene-remote-list.cpp index 51afcb48..aacc537f 100644 --- a/applications/irda/scene/irda-app-scene-remote-list.cpp +++ b/applications/irda/scene/irda-app-scene-remote-list.cpp @@ -20,13 +20,26 @@ void IrdaAppSceneRemoteList::on_enter(IrdaApp* app) { auto remote_manager = app->get_remote_manager(); int i = 0; - remote_names = remote_manager->get_remote_list(); - for(auto& a : remote_names) { - submenu_add_item(submenu, a.c_str(), i++, submenu_callback, app); + bool result = remote_manager->get_remote_list(remote_names); + if(!result) { + app->switch_to_previous_scene(); + return; + } + + for(auto& name : remote_names) { + submenu_add_item(submenu, name.c_str(), i++, submenu_callback, app); } submenu_add_item( submenu, " +", SubmenuIndexPlus, submenu_callback, app); + if((SubmenuIndex)submenu_item_selected == SubmenuIndexPlus) { + submenu_set_selected_item(submenu, submenu_item_selected); + } else { + int remote_index = remote_manager->find_remote_name(remote_names); + submenu_set_selected_item(submenu, (remote_index >= 0) ? remote_index : 0); + } + + submenu_item_selected = 0; view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); } @@ -38,11 +51,14 @@ bool IrdaAppSceneRemoteList::on_event(IrdaApp* app, IrdaAppEvent* event) { case SubmenuIndexPlus: app->set_learn_new_remote(true); app->switch_to_next_scene(IrdaApp::Scene::Learn); + submenu_item_selected = event->payload.menu_index; break; default: auto remote_manager = app->get_remote_manager(); - remote_manager->set_current_remote(event->payload.menu_index); - app->switch_to_next_scene(IrdaApp::Scene::Remote); + bool result = remote_manager->load(remote_names.at(event->payload.menu_index)); + if(result) { + app->switch_to_next_scene(IrdaApp::Scene::Remote); + } consumed = true; break; } diff --git a/applications/irda/scene/irda-app-scene-remote.cpp b/applications/irda/scene/irda-app-scene-remote.cpp index ba65ead0..ac33fcad 100644 --- a/applications/irda/scene/irda-app-scene-remote.cpp +++ b/applications/irda/scene/irda-app-scene-remote.cpp @@ -4,6 +4,7 @@ typedef enum { ButtonIndexPlus = -2, ButtonIndexEdit = -1, + ButtonIndexNA = 0, } ButtonIndex; static void button_menu_callback(void* context, int32_t index) { @@ -35,8 +36,12 @@ void IrdaAppSceneRemote::on_enter(IrdaApp* app) { button_menu_add_item( button_menu, "Edit", ButtonIndexEdit, button_menu_callback, ButtonMenuItemTypeControl, app); - app->set_text_store(0, "%s", remote_manager->get_current_remote_name().c_str()); + app->set_text_store(0, "%s", remote_manager->get_remote_name().c_str()); button_menu_set_header(button_menu, app->get_text_store(0)); + if(buttonmenu_item_selected != ButtonIndexNA) { + button_menu_set_selected_item(button_menu, buttonmenu_item_selected); + buttonmenu_item_selected = ButtonIndexNA; + } view_manager->switch_to(IrdaAppViewManager::ViewType::ButtonMenu); } @@ -46,12 +51,18 @@ bool IrdaAppSceneRemote::on_event(IrdaApp* app, IrdaAppEvent* event) { if(event->type == IrdaAppEvent::Type::MenuSelected) { switch(event->payload.menu_index) { case ButtonIndexPlus: + app->notify_click(); + buttonmenu_item_selected = event->payload.menu_index; + app->set_learn_new_remote(false); app->switch_to_next_scene(IrdaApp::Scene::Learn); break; case ButtonIndexEdit: + app->notify_click(); + buttonmenu_item_selected = event->payload.menu_index; app->switch_to_next_scene(IrdaApp::Scene::Edit); break; default: + app->notify_click_and_blink(); auto remote_manager = app->get_remote_manager(); auto message = remote_manager->get_button_data(event->payload.menu_index); app->get_receiver()->send_message(message); diff --git a/applications/irda/scene/irda-app-scene-start.cpp b/applications/irda/scene/irda-app-scene-start.cpp index 7efb6ccb..27f40ad3 100644 --- a/applications/irda/scene/irda-app-scene-start.cpp +++ b/applications/irda/scene/irda-app-scene-start.cpp @@ -25,6 +25,8 @@ void IrdaAppSceneStart::on_enter(IrdaApp* app) { submenu_add_item( submenu, "Learn new remote", SubmenuIndexLearnNewRemote, submenu_callback, app); submenu_add_item(submenu, "Saved remotes", SubmenuIndexSavedRemotes, submenu_callback, app); + submenu_set_selected_item(submenu, submenu_item_selected); + submenu_item_selected = 0; view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); } @@ -33,6 +35,7 @@ bool IrdaAppSceneStart::on_event(IrdaApp* app, IrdaAppEvent* event) { bool consumed = false; if(event->type == IrdaAppEvent::Type::MenuSelected) { + submenu_item_selected = event->payload.menu_index; switch(event->payload.menu_index) { case SubmenuIndexUniversalLibrary: app->switch_to_next_scene(IrdaApp::Scene::Universal); @@ -44,6 +47,9 @@ bool IrdaAppSceneStart::on_event(IrdaApp* app, IrdaAppEvent* event) { case SubmenuIndexSavedRemotes: app->switch_to_next_scene(IrdaApp::Scene::RemoteList); break; + default: + furi_assert(0); + break; } consumed = true; } diff --git a/applications/irda/scene/irda-app-scene-universal.cpp b/applications/irda/scene/irda-app-scene-universal.cpp index 2ab4ae2e..949f995b 100644 --- a/applications/irda/scene/irda-app-scene-universal.cpp +++ b/applications/irda/scene/irda-app-scene-universal.cpp @@ -24,6 +24,8 @@ void IrdaAppSceneUniversal::on_enter(IrdaApp* app) { submenu_add_item(submenu, "Audio Players", SubmenuIndexUniversalAudio, submenu_callback, app); submenu_add_item( submenu, "Air Conditioners", SubmenuIndexUniversalAirConditioner, submenu_callback, app); + submenu_set_selected_item(submenu, submenu_item_selected); + submenu_item_selected = 0; view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); } @@ -32,6 +34,7 @@ bool IrdaAppSceneUniversal::on_event(IrdaApp* app, IrdaAppEvent* event) { bool consumed = false; if(event->type == IrdaAppEvent::Type::MenuSelected) { + submenu_item_selected = event->payload.menu_index; switch(event->payload.menu_index) { case SubmenuIndexUniversalTV: // app->switch_to_next_scene(IrdaApp::Scene::UniversalTV); diff --git a/applications/irda/scene/irda-app-scene.hpp b/applications/irda/scene/irda-app-scene.hpp index 832e4bc0..b21d435a 100644 --- a/applications/irda/scene/irda-app-scene.hpp +++ b/applications/irda/scene/irda-app-scene.hpp @@ -23,6 +23,8 @@ public: void on_enter(IrdaApp* app) final; bool on_event(IrdaApp* app, IrdaAppEvent* event) final; void on_exit(IrdaApp* app) final; +private: + uint32_t submenu_item_selected = 0; }; class IrdaAppSceneUniversal : public IrdaAppScene { @@ -30,6 +32,8 @@ public: void on_enter(IrdaApp* app) final; bool on_event(IrdaApp* app, IrdaAppEvent* event) final; void on_exit(IrdaApp* app) final; +private: + uint32_t submenu_item_selected = 0; }; class IrdaAppSceneLearn : public IrdaAppScene { @@ -74,6 +78,7 @@ public: void on_exit(IrdaApp* app) final; private: std::vector buttons_names; + uint32_t buttonmenu_item_selected = 0; }; class IrdaAppSceneRemoteList : public IrdaAppScene { @@ -81,6 +86,8 @@ public: void on_enter(IrdaApp* app) final; bool on_event(IrdaApp* app, IrdaAppEvent* event) final; void on_exit(IrdaApp* app) final; +private: + uint32_t submenu_item_selected = 0; std::vector remote_names; }; @@ -89,6 +96,8 @@ public: void on_enter(IrdaApp* app) final; bool on_event(IrdaApp* app, IrdaAppEvent* event) final; void on_exit(IrdaApp* app) final; +private: + uint32_t submenu_item_selected = 0; }; class IrdaAppSceneEditKeySelect : public IrdaAppScene { diff --git a/applications/notification/notification-messages-notes.c b/applications/notification/notification-messages-notes.c index 5d0a0350..d9324a06 100644 --- a/applications/notification/notification-messages-notes.c +++ b/applications/notification/notification-messages-notes.c @@ -26,6 +26,11 @@ for octave in range(9): print(f"extern const NotificationMessage message_note_{name}{octave};") */ +const NotificationMessage message_click = { + .type = NotificationMessageTypeSoundOn, + .data.sound.frequency = 1.0f, + .data.sound.pwm = 0.5f, +}; const NotificationMessage message_note_c0 = { .type = NotificationMessageTypeSoundOn, .data.sound.frequency = 16.35f, @@ -565,4 +570,4 @@ const NotificationMessage message_note_b8 = { .type = NotificationMessageTypeSoundOn, .data.sound.frequency = 7902.13f, .data.sound.pwm = 0.5f, -}; \ No newline at end of file +}; diff --git a/applications/notification/notification-messages-notes.h b/applications/notification/notification-messages-notes.h index 7ba4b609..b1040a01 100644 --- a/applications/notification/notification-messages-notes.h +++ b/applications/notification/notification-messages-notes.h @@ -5,6 +5,7 @@ extern "C" { #endif +extern const NotificationMessage message_click; extern const NotificationMessage message_note_c0; extern const NotificationMessage message_note_cs0; extern const NotificationMessage message_note_d0; diff --git a/applications/notification/notification-messages.c b/applications/notification/notification-messages.c index b1dcceda..ff4b5d79 100644 --- a/applications/notification/notification-messages.c +++ b/applications/notification/notification-messages.c @@ -48,6 +48,11 @@ const NotificationMessage message_blue_0 = { }; // Delay +const NotificationMessage message_delay_1 = { + .type = NotificationMessageTypeDelay, + .data.delay.length = 1, +}; + const NotificationMessage message_delay_10 = { .type = NotificationMessageTypeDelay, .data.delay.length = 10, @@ -113,12 +118,12 @@ const NotificationSequence sequence_reset_red = { }; const NotificationSequence sequence_reset_green = { - &message_blue_0, + &message_green_0, NULL, }; const NotificationSequence sequence_reset_blue = { - &message_green_0, + &message_blue_0, NULL, }; @@ -298,6 +303,17 @@ const NotificationSequence sequence_blink_white_100 = { }; // General +const NotificationSequence sequence_double_vibro = { + &message_vibro_on, + &message_delay_100, + &message_vibro_off, + &message_delay_100, + &message_vibro_on, + &message_delay_100, + &message_vibro_off, + NULL, +}; + const NotificationSequence sequence_success = { &message_display_on, &message_green_255, diff --git a/applications/notification/notification-messages.h b/applications/notification/notification-messages.h index ab5a9472..fc083eba 100644 --- a/applications/notification/notification-messages.h +++ b/applications/notification/notification-messages.h @@ -23,6 +23,7 @@ extern const NotificationMessage message_green_0; extern const NotificationMessage message_blue_0; // Delay +extern const NotificationMessage message_delay_1; extern const NotificationMessage message_delay_10; extern const NotificationMessage message_delay_25; extern const NotificationMessage message_delay_50; @@ -87,6 +88,7 @@ extern const NotificationSequence sequence_blink_magenta_100; extern const NotificationSequence sequence_blink_white_100; // General +extern const NotificationSequence sequence_double_vibro; extern const NotificationSequence sequence_success; extern const NotificationSequence sequence_error; diff --git a/firmware/targets/api-hal-include/api-hal-irda.h b/firmware/targets/api-hal-include/api-hal-irda.h index d87faa55..b53dccd0 100644 --- a/firmware/targets/api-hal-include/api-hal-irda.h +++ b/firmware/targets/api-hal-include/api-hal-irda.h @@ -49,6 +49,12 @@ void api_hal_irda_pwm_set(float duty_cycle, float freq); */ void api_hal_irda_pwm_stop(); +/** + * Check if IRDA is in use now. + * @return false - IRDA is busy, true otherwise. + */ +bool api_hal_irda_rx_irq_is_busy(void); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f5/api-hal/api-hal-irda.c b/firmware/targets/f5/api-hal/api-hal-irda.c index 564218a9..434c7037 100644 --- a/firmware/targets/f5/api-hal/api-hal-irda.c +++ b/firmware/targets/f5/api-hal/api-hal-irda.c @@ -97,6 +97,10 @@ void api_hal_irda_rx_irq_deinit(void) { LL_TIM_CC_DisableChannel(TIM2, LL_TIM_CHANNEL_CH2); } +bool api_hal_irda_rx_irq_is_busy(void) { + return (LL_TIM_IsEnabledIT_CC1(TIM2) || LL_TIM_IsEnabledIT_CC2(TIM2)); +} + void api_hal_irda_rx_irq_set_callback(TimerISRCallback callback, void *ctx) { furi_check(callback); diff --git a/firmware/targets/f6/api-hal/api-hal-irda.c b/firmware/targets/f6/api-hal/api-hal-irda.c index 564218a9..434c7037 100644 --- a/firmware/targets/f6/api-hal/api-hal-irda.c +++ b/firmware/targets/f6/api-hal/api-hal-irda.c @@ -97,6 +97,10 @@ void api_hal_irda_rx_irq_deinit(void) { LL_TIM_CC_DisableChannel(TIM2, LL_TIM_CHANNEL_CH2); } +bool api_hal_irda_rx_irq_is_busy(void) { + return (LL_TIM_IsEnabledIT_CC1(TIM2) || LL_TIM_IsEnabledIT_CC2(TIM2)); +} + void api_hal_irda_rx_irq_set_callback(TimerISRCallback callback, void *ctx) { furi_check(callback); diff --git a/lib/irda/irda.c b/lib/irda/irda.c index 0d9dfc4b..48159f64 100644 --- a/lib/irda/irda.c +++ b/lib/irda/irda.c @@ -1,3 +1,4 @@ +#include "irda.h" #include #include #include @@ -58,7 +59,7 @@ static const IrdaProtocolImplementation irda_protocols[] = { .address_length = 2, .command_length = 2, }, - // #2 + // #2 - have to be after NEC { .protocol = IrdaProtocolNECext, .name = "NECext", .decoder = { @@ -81,10 +82,12 @@ const IrdaMessage* irda_decode(IrdaHandler* handler, bool level, uint32_t durati IrdaMessage* result = NULL; for (int i = 0; i < COUNT_OF(irda_protocols); ++i) { - message = irda_protocols[i].decoder.decode(handler->ctx[i], level, duration); - if (!result && message) { - message->protocol = irda_protocols[i].protocol; - result = message; + if (irda_protocols[i].decoder.decode) { + message = irda_protocols[i].decoder.decode(handler->ctx[i], level, duration); + if (!result && message) { + message->protocol = irda_protocols[i].protocol; + result = message; + } } } @@ -96,8 +99,9 @@ IrdaHandler* irda_alloc_decoder(void) { handler->ctx = furi_alloc(sizeof(void*) * COUNT_OF(irda_protocols)); for (int i = 0; i < COUNT_OF(irda_protocols); ++i) { - handler->ctx[i] = irda_protocols[i].decoder.alloc(); - furi_check(handler->ctx[i]); + handler->ctx[i] = 0; + if (irda_protocols[i].decoder.alloc) + handler->ctx[i] = irda_protocols[i].decoder.alloc(); } return handler; @@ -108,7 +112,8 @@ void irda_free_decoder(IrdaHandler* handler) { furi_assert(handler->ctx); for (int i = 0; i < COUNT_OF(irda_protocols); ++i) { - irda_protocols[i].decoder.free(handler->ctx[i]); + if (irda_protocols[i].decoder.free) + irda_protocols[i].decoder.free(handler->ctx[i]); } free(handler->ctx); @@ -117,31 +122,54 @@ void irda_free_decoder(IrdaHandler* handler) { void irda_reset_decoder(IrdaHandler* handler) { for (int i = 0; i < COUNT_OF(irda_protocols); ++i) { - irda_protocols[i].decoder.reset(handler->ctx[i]); + if (irda_protocols[i].decoder.reset) + irda_protocols[i].decoder.reset(handler->ctx[i]); } } void irda_send(const IrdaMessage* message, int times) { furi_assert(message); + furi_assert(irda_is_protocol_valid(message->protocol)); for (int i = 0; i < times; ++i) { - osKernelLock(); - __disable_irq(); - irda_protocols[message->protocol].encoder.encode(message->address, message->command, !!i); - __enable_irq(); - osKernelUnlock(); + if(irda_protocols[message->protocol].encoder.encode) { + __disable_irq(); + irda_protocols[message->protocol].encoder.encode(message->address, message->command, !!i); + __enable_irq(); + } } } +bool irda_is_protocol_valid(IrdaProtocol protocol) { + return (protocol >= 0) && (protocol < COUNT_OF(irda_protocols)); +} + +IrdaProtocol irda_get_protocol_by_name(const char* protocol_name) { + for (int i = 0; i < COUNT_OF(irda_protocols); ++i) { + if (!strcmp(irda_protocols[i].name, protocol_name)) + return i; + } + return IrdaProtocolUnknown; +} + const char* irda_get_protocol_name(IrdaProtocol protocol) { - return irda_protocols[protocol].name; + if (irda_is_protocol_valid(protocol)) + return irda_protocols[protocol].name; + else + return "Invalid"; } uint8_t irda_get_protocol_address_length(IrdaProtocol protocol) { - return irda_protocols[protocol].address_length; + if (irda_is_protocol_valid(protocol)) + return irda_protocols[protocol].address_length; + else + return 0; } uint8_t irda_get_protocol_command_length(IrdaProtocol protocol) { - return irda_protocols[protocol].command_length; + if (irda_is_protocol_valid(protocol)) + return irda_protocols[protocol].command_length; + else + return 0; } diff --git a/lib/irda/irda.h b/lib/irda/irda.h index 42411724..1ac6a27f 100644 --- a/lib/irda/irda.h +++ b/lib/irda/irda.h @@ -11,6 +11,7 @@ typedef struct IrdaHandler IrdaHandler; // Do not change protocol order, as it can be saved into memory and fw update can be performed! typedef enum { + IrdaProtocolUnknown = -1, IrdaProtocolSamsung32 = 0, IrdaProtocolNEC = 1, IrdaProtocolNECext = 2, @@ -74,6 +75,14 @@ void irda_send(const IrdaMessage* message, int times); */ const char* irda_get_protocol_name(IrdaProtocol protocol); +/** + * Get protocol enum by protocol name. + * + * \param[in] protocol_name - string to protocol name. + * \return protocol identifier. + */ +IrdaProtocol irda_get_protocol_by_name(const char* protocol_name); + /** * Get address length by protocol enum. * @@ -90,6 +99,14 @@ uint8_t irda_get_protocol_address_length(IrdaProtocol protocol); */ uint8_t irda_get_protocol_command_length(IrdaProtocol protocol); +/** + * Checks whether protocol valid. + * + * \param[in] protocol - protocol identifier. + * \return true if protocol is valid, false otherwise. + */ +bool irda_is_protocol_valid(IrdaProtocol protocol); + #ifdef __cplusplus } #endif