Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IR: Easy Learn #350

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions applications/main/infrared/infrared_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ static InfraredApp* infrared_alloc(void) {
InfraredAppState* app_state = &infrared->app_state;
app_state->is_learning_new_remote = false;
app_state->is_debug_enabled = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug);
app_state->is_transmitting = false;
app_state->is_otg_enabled = false;
app_state->is_easy_mode = false;
app_state->edit_target = InfraredEditTargetNone;
app_state->edit_mode = InfraredEditModeNone;
app_state->current_button_index = InfraredButtonIndexNone;
Expand Down Expand Up @@ -517,12 +520,14 @@ static void infrared_load_settings(InfraredApp* infrared) {
if(settings.tx_pin < FuriHalInfraredTxPinMax) {
infrared_enable_otg(infrared, settings.otg_enabled);
}
infrared->app_state.is_easy_mode = settings.easy_mode;
}

void infrared_save_settings(InfraredApp* infrared) {
InfraredSettings settings = {
.tx_pin = infrared->app_state.tx_pin,
.otg_enabled = infrared->app_state.is_otg_enabled,
.easy_mode = infrared->app_state.is_easy_mode,
};

if(!saved_struct_save(
Expand Down
1 change: 1 addition & 0 deletions applications/main/infrared/infrared_app.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ typedef struct InfraredApp InfraredApp;
typedef struct {
FuriHalInfraredTxPin tx_pin;
bool otg_enabled;
bool easy_mode;
} InfraredSettings;
7 changes: 7 additions & 0 deletions applications/main/infrared/infrared_app_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
#define INFRARED_DEFAULT_REMOTE_NAME "Remote"
#define INFRARED_LOG_TAG "InfraredApp"

/* Button names for easy mode */
#define EASY_MODE_BUTTON_COUNT 23 // Number of buttons in the array
extern const char* const easy_mode_button_names[];

/**
* @brief Enumeration of invalid remote button indices.
*/
Expand Down Expand Up @@ -85,9 +89,12 @@ typedef struct {
bool is_debug_enabled; /**< Whether to enable or disable debugging features. */
bool is_transmitting; /**< Whether a signal is currently being transmitted. */
bool is_otg_enabled; /**< Whether OTG power (external 5V) is enabled. */
bool is_easy_mode; /**< Whether easy learning mode is enabled. */
InfraredEditTarget edit_target : 8; /**< Selected editing target (a remote or a button). */
InfraredEditMode edit_mode : 8; /**< Selected editing operation (rename or delete). */
int32_t current_button_index; /**< Selected button index (move destination). */
int32_t
existing_remote_button_index; /**< Current button index for existing remotes in easy mode. */
int32_t prev_button_index; /**< Previous button index (move source). */
uint32_t last_transmit_time; /**< Lat time a signal was transmitted. */
FuriHalInfraredTxPin tx_pin;
Expand Down
172 changes: 159 additions & 13 deletions applications/main/infrared/scenes/infrared_scene_learn.c
Original file line number Diff line number Diff line change
@@ -1,23 +1,153 @@
#include "../infrared_app_i.h"
#include <dolphin/dolphin.h>

/* Button names for easy mode */
const char* const easy_mode_button_names[] = {"Power", "Vol_up", "Vol_dn", "Ch_up", "Ch_dn",
"Mute", "Eject", "Input", "Back", "Ok",
"Up", "Down", "Left", "Right", "Play",
"Pause", "Stop", "Prev", "Next", "Rew",
"FF", "Exit", "Menu"};

static void infrared_scene_learn_dialog_result_callback(DialogExResult result, void* context) {
InfraredApp* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
}

static bool infrared_scene_learn_get_next_name(
InfraredApp* infrared,
int32_t start_index,
int32_t* next_index) {
if(!infrared->remote) return false;

// Search through remaining button names to find one that doesn't exist
for(int32_t i = start_index; i < (int32_t)EASY_MODE_BUTTON_COUNT; i++) {
const char* name = easy_mode_button_names[i];
bool name_exists = false;

// Check if this name already exists in remote
for(size_t j = 0; j < infrared_remote_get_signal_count(infrared->remote); j++) {
if(strcmp(name, infrared_remote_get_signal_name(infrared->remote, j)) == 0) {
name_exists = true;
break;
}
}

// If we found a name that doesn't exist, return it
if(!name_exists) {
*next_index = i;
return true;
}
}

return false;
}

static void infrared_scene_learn_update_button_name(InfraredApp* infrared, bool increment) {
DialogEx* dialog_ex = infrared->dialog_ex;
int32_t button_index;

if(infrared->app_state.is_learning_new_remote) {
// For new remotes, use current_button_index directly
button_index = infrared->app_state.current_button_index;
if(increment) {
// Only increment if we haven't reached the last button
if(button_index + 1 < (int32_t)EASY_MODE_BUTTON_COUNT) {
button_index++;
infrared->app_state.current_button_index = button_index;
}
}
} else if(infrared->remote) {
// For existing remotes, find next available button name
button_index = infrared->app_state.existing_remote_button_index;
if(increment) {
int32_t next_index;
if(infrared_scene_learn_get_next_name(infrared, button_index + 1, &next_index)) {
button_index = next_index;
infrared->app_state.existing_remote_button_index = button_index;
}
}
} else {
button_index = 0;
}

// Ensure button_index is valid
if(button_index < 0) button_index = 0;
if(button_index >= (int32_t)EASY_MODE_BUTTON_COUNT) {
button_index = (int32_t)EASY_MODE_BUTTON_COUNT - 1;
}

// Now we know button_index is valid, use it to get the name
const char* button_name = easy_mode_button_names[button_index];

infrared_text_store_set(
infrared, 0, "Point remote at IR port\nand press the %s button", button_name);
dialog_ex_set_text(dialog_ex, infrared->text_store[0], 5, 10, AlignLeft, AlignCenter);

// For existing remotes, check if there are any more buttons to add
bool has_more_buttons = false;
if(!infrared->app_state.is_learning_new_remote && infrared->remote) {
int32_t next_index;
has_more_buttons =
infrared_scene_learn_get_next_name(infrared, button_index + 1, &next_index);
} else {
has_more_buttons = (button_index + 1 < (int32_t)EASY_MODE_BUTTON_COUNT);
}

// Show/hide skip button based on whether there are more buttons
if(!has_more_buttons) {
dialog_ex_set_center_button_text(dialog_ex, NULL);
} else {
dialog_ex_set_center_button_text(dialog_ex, "Skip");
}
}

void infrared_scene_learn_on_enter(void* context) {
InfraredApp* infrared = context;
Popup* popup = infrared->popup;
DialogEx* dialog_ex = infrared->dialog_ex;
InfraredWorker* worker = infrared->worker;

// Initialize or validate current_button_index
if(infrared->app_state.is_learning_new_remote) {
// If index is beyond our predefined names, reset it
if(infrared->app_state.current_button_index >= (int32_t)EASY_MODE_BUTTON_COUNT) {
infrared->app_state.current_button_index = 0;
}
} else {
// For existing remotes, find first missing button name
int32_t next_index;
if(infrared_scene_learn_get_next_name(infrared, 0, &next_index)) {
infrared->app_state.existing_remote_button_index = next_index;
} else {
// If no missing buttons found, start at beginning
infrared->app_state.existing_remote_button_index = 0;
}
}

infrared_worker_rx_set_received_signal_callback(
worker, infrared_signal_received_callback, context);
infrared_worker_rx_start(worker);
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartRead);

popup_set_icon(popup, 0, 32, &I_InfraredLearnShort_128x31);
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignCenter);
popup_set_text(
popup, "Point the remote at IR port\nand push the button", 5, 10, AlignLeft, AlignCenter);
popup_set_callback(popup, NULL);
dialog_ex_set_icon(dialog_ex, 0, 32, &I_InfraredLearnShort_128x31);
dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);

view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
if(infrared->app_state.is_easy_mode) {
infrared_scene_learn_update_button_name(infrared, false);
dialog_ex_set_icon(dialog_ex, 0, 22, &I_InfraredLearnShort_128x31);
} else {
dialog_ex_set_text(
dialog_ex,
"Point the remote at IR port\nand push the button",
5,
13,
AlignLeft,
AlignCenter);
}

dialog_ex_set_context(dialog_ex, context);
dialog_ex_set_result_callback(dialog_ex, infrared_scene_learn_dialog_result_callback);

view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
}

bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) {
Expand All @@ -26,22 +156,38 @@ bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) {

if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeSignalReceived) {
infrared_play_notification_message(infrared, InfraredNotificationMessageSuccess);
scene_manager_next_scene(infrared->scene_manager, InfraredSceneLearnSuccess);
dolphin_deed(DolphinDeedIrLearnSuccess);
consumed = true;
} else if(event.event == DialogExResultCenter && infrared->app_state.is_easy_mode) {
// Update with increment when skipping
infrared_scene_learn_update_button_name(infrared, true);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
// Reset button indices when exiting learn mode completely
if(infrared->app_state.is_learning_new_remote) {
infrared->app_state.current_button_index = 0;
} else {
infrared->app_state.existing_remote_button_index = 0;
}
consumed = false;
}

return consumed;
}

void infrared_scene_learn_on_exit(void* context) {
InfraredApp* infrared = context;
Popup* popup = infrared->popup;
infrared_worker_rx_set_received_signal_callback(infrared->worker, NULL, NULL);

// Reset dialog
dialog_ex_reset(infrared->dialog_ex);

// Stop worker and clear callback
infrared_worker_rx_stop(infrared->worker);
infrared_worker_rx_set_received_signal_callback(infrared->worker, NULL, NULL);

// Clear any stored text
infrared_text_store_clear(infrared, 0);

infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
popup_set_icon(popup, 0, 0, NULL);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignCenter);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,24 @@ void infrared_scene_learn_enter_name_on_enter(void* context) {
TextInput* text_input = infrared->text_input;
InfraredSignal* signal = infrared->current_signal;

if(infrared_signal_is_raw(signal)) {
if(infrared->app_state.is_easy_mode) {
// In easy mode, use predefined names based on button index
int32_t button_index;
if(infrared->app_state.is_learning_new_remote) {
button_index = infrared->app_state.current_button_index;
} else {
button_index = infrared->app_state.existing_remote_button_index;
}

// Ensure button_index is valid
if(button_index < 0) button_index = 0;
if(button_index >= (int32_t)EASY_MODE_BUTTON_COUNT) {
button_index = (int32_t)EASY_MODE_BUTTON_COUNT - 1;
}

// Always use predefined names in easy mode
infrared_text_store_set(infrared, 0, "%s", easy_mode_button_names[button_index]);
} else if(infrared_signal_is_raw(signal)) {
const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
infrared_text_store_set(infrared, 0, "RAW_%zu", raw->timings_size);
} else {
Expand All @@ -27,7 +44,7 @@ void infrared_scene_learn_enter_name_on_enter(void* context) {
context,
infrared->text_store[0],
INFRARED_MAX_BUTTON_NAME_LENGTH,
true);
!infrared->app_state.is_easy_mode); // Only allow editing in normal mode

view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput);
}
Expand Down
Loading