Skip to content

Commit

Permalink
[FL-3618] Infrared remote button index support (#3180)
Browse files Browse the repository at this point in the history
* Do not load all signals at once (Draft)
* Minor cleanup
* Refactor remote renaming
* Improve function signatures
* Rename infrared_remote functions
* Optimise signal loading
* Implement adding signals to remote
* Add read_name() method
* Deprecate a function
* Partially implement deleting signals (draft)
* Use m-array instead of m-list for signal name directory
* Use plain C strings instead of furi_string
* Implement deleting signals
* Implement deleting signals via generalised callback
* Implement renaming signals
* Rename some types
* Some more renaming
* Remove unused type
* Implement inserting signals (internal use)
* Improve InfraredMoveView
* Send an event to move a signal
* Remove unused type
* Implement moving signals
* Implement creating new remotes with one signal
* Un-deprecate and rename a function
* Add InfraredRemote API docs
* Add InfraredSignal API docs
* Better error messages
* Show progress pop-up when moving buttons in a remote
* Copy labels to the InfraredMoveView to avoid pointer invalidation
* Improve file selection scene
* Show progress pop-up when renaming buttons in a remote
* Refactor a scene
* Show progress when deleting a button from remote
* Use a random name for temp files
* Add docs to infrared_brute_force.h
* Rename Infrared type to InfraredApp
* Add docs to infrared_app_i.h
* Deliver event data via a callback
* Bundle event data together with event type
* Change DataExchange behaviour
* Adapt RPC debug app to new API
* Remove rogue output
* Add Doxygen comments to rpc_app.h
* Simplify rpc_app.c code
* Remove superflous parameter
* Do not allocate protobuf messages on the stack
* Fix GetError response
* Support for button indices
* Comment out shallow submodules
* Fix F18 api
* Fix logical error and add more debug output
* fbt: testing unshallow for protobuf
* github: lint&checks: unshallow prior to checks
* Fix a TODO
* github: do not unshallow the unshallowed
* fbt: assets: only attempt to unshallow if cannot describe
* Do not use the name when loading a signal by index (duh)
* Simplify loading infrared signals by name
* Sync with protobuf release
* Infrared: use compact furi_crash macros

Co-authored-by: hedger <[email protected]>
Co-authored-by: hedger <[email protected]>
Co-authored-by: Aleksandr Kutuzov <[email protected]>
  • Loading branch information
4 people authored Nov 10, 2023
1 parent 16ffa2b commit 49dcf81
Show file tree
Hide file tree
Showing 29 changed files with 670 additions and 415 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint_and_submodule_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:

- name: 'Check protobuf branch'
run: |
git submodule update --init
git submodule update --init;
SUB_PATH="assets/protobuf";
SUB_BRANCH="dev";
SUB_COMMITS_MIN=40;
Expand Down
39 changes: 34 additions & 5 deletions applications/debug/rpc_debug_app/rpc_debug_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,51 @@ static void rpc_debug_app_tick_event_callback(void* context) {
scene_manager_handle_tick_event(app->scene_manager);
}

static void rpc_debug_app_rpc_command_callback(RpcAppSystemEvent event, void* context) {
static void
rpc_debug_app_format_hex(const uint8_t* data, size_t data_size, char* buf, size_t buf_size) {
if(data == NULL || data_size == 0) {
strncpy(buf, "<Data empty>", buf_size);
return;
}

const size_t byte_width = 3;
const size_t line_width = 7;

data_size = MIN(data_size, buf_size / (byte_width + 1));

for(size_t i = 0; i < data_size; ++i) {
char* p = buf + (i * byte_width);
char sep = !((i + 1) % line_width) ? '\n' : ' ';
snprintf(p, byte_width + 1, "%02X%c", data[i], sep);
}

buf[buf_size - 1] = '\0';
}

static void rpc_debug_app_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
furi_assert(context);
RpcDebugApp* app = context;
furi_assert(app->rpc);

if(event == RpcAppEventSessionClose) {
if(event->type == RpcAppEventTypeSessionClose) {
scene_manager_stop(app->scene_manager);
view_dispatcher_stop(app->view_dispatcher);
rpc_system_app_set_callback(app->rpc, NULL, NULL);
app->rpc = NULL;
} else if(event == RpcAppEventAppExit) {
} else if(event->type == RpcAppEventTypeAppExit) {
scene_manager_stop(app->scene_manager);
view_dispatcher_stop(app->view_dispatcher);
rpc_system_app_confirm(app->rpc, RpcAppEventAppExit, true);
rpc_system_app_confirm(app->rpc, true);
} else if(event->type == RpcAppEventTypeDataExchange) {
furi_assert(event->data.type == RpcAppSystemEventDataTypeBytes);

rpc_debug_app_format_hex(
event->data.bytes.ptr, event->data.bytes.size, app->text_store, TEXT_STORE_SIZE);

view_dispatcher_send_custom_event(
app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange);
} else {
rpc_system_app_confirm(app->rpc, event, false);
rpc_system_app_confirm(app->rpc, false);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,12 @@
#include "../rpc_debug_app.h"

static void rpc_debug_app_scene_start_format_hex(
const uint8_t* data,
size_t data_size,
char* buf,
size_t buf_size) {
furi_assert(data);
furi_assert(buf);

const size_t byte_width = 3;
const size_t line_width = 7;

data_size = MIN(data_size, buf_size / (byte_width + 1));

for(size_t i = 0; i < data_size; ++i) {
char* p = buf + (i * byte_width);
char sep = !((i + 1) % line_width) ? '\n' : ' ';
snprintf(p, byte_width + 1, "%02X%c", data[i], sep);
}

buf[buf_size - 1] = '\0';
}

static void rpc_debug_app_scene_receive_data_exchange_callback(
const uint8_t* data,
size_t data_size,
void* context) {
RpcDebugApp* app = context;
if(data) {
rpc_debug_app_scene_start_format_hex(data, data_size, app->text_store, TEXT_STORE_SIZE);
} else {
strncpy(app->text_store, "<Data empty>", TEXT_STORE_SIZE);
}
view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange);
}

void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) {
RpcDebugApp* app = context;
strncpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE);

text_box_set_text(app->text_box, app->text_store);
text_box_set_font(app->text_box, TextBoxFontHex);

rpc_system_app_set_data_exchange_callback(
app->rpc, rpc_debug_app_scene_receive_data_exchange_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextBox);
}

Expand All @@ -53,6 +16,7 @@ bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneMana

if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RpcDebugAppCustomEventRpcDataExchange) {
rpc_system_app_confirm(app->rpc, true);
notification_message(app->notifications, &sequence_blink_cyan_100);
notification_message(app->notifications, &sequence_display_backlight_on);
text_box_set_text(app->text_box, app->text_store);
Expand All @@ -66,5 +30,4 @@ bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneMana
void rpc_debug_app_scene_receive_data_exchange_on_exit(void* context) {
RpcDebugApp* app = context;
text_box_reset(app->text_box);
rpc_system_app_set_data_exchange_callback(app->rpc, NULL, NULL);
}
14 changes: 8 additions & 6 deletions applications/main/ibutton/ibutton.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,23 @@ static void ibutton_make_app_folder(iButton* ibutton) {
furi_record_close(RECORD_STORAGE);
}

static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) {
static void ibutton_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
furi_assert(context);
iButton* ibutton = context;

if(event == RpcAppEventSessionClose) {
if(event->type == RpcAppEventTypeSessionClose) {
view_dispatcher_send_custom_event(
ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose);
rpc_system_app_set_callback(ibutton->rpc, NULL, NULL);
ibutton->rpc = NULL;
} else if(event == RpcAppEventAppExit) {
} else if(event->type == RpcAppEventTypeAppExit) {
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
} else if(event == RpcAppEventLoadFile) {
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoad);
} else if(event->type == RpcAppEventTypeLoadFile) {
furi_assert(event->data.type == RpcAppSystemEventDataTypeString);
furi_string_set(ibutton->file_path, event->data.string);
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoadFile);
} else {
rpc_system_app_confirm(ibutton->rpc, event, false);
rpc_system_app_confirm(ibutton->rpc, false);
}
}

Expand Down
2 changes: 1 addition & 1 deletion applications/main/ibutton/ibutton_custom_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ typedef enum {
iButtonCustomEventWorkerWriteNoDetect,
iButtonCustomEventWorkerWriteCannotWrite,

iButtonCustomEventRpcLoad,
iButtonCustomEventRpcLoadFile,
iButtonCustomEventRpcExit,
iButtonCustomEventRpcSessionClose,
} iButtonCustomEvent;
23 changes: 9 additions & 14 deletions applications/main/ibutton/scenes/ibutton_scene_rpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,23 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;

if(event.event == iButtonCustomEventRpcLoad) {
if(event.event == iButtonCustomEventRpcLoadFile) {
bool result = false;
const char* file_path = rpc_system_app_get_data(ibutton->rpc);

if(file_path && (furi_string_empty(ibutton->file_path))) {
furi_string_set(ibutton->file_path, file_path);
if(ibutton_load_key(ibutton)) {
popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);

if(ibutton_load_key(ibutton)) {
popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
ibutton_worker_emulate_start(ibutton->worker, ibutton->key);

ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
ibutton_worker_emulate_start(ibutton->worker, ibutton->key);

result = true;
}
result = true;
}

rpc_system_app_confirm(ibutton->rpc, RpcAppEventLoadFile, result);
rpc_system_app_confirm(ibutton->rpc, result);

} else if(event.event == iButtonCustomEventRpcExit) {
rpc_system_app_confirm(ibutton->rpc, RpcAppEventAppExit, true);
rpc_system_app_confirm(ibutton->rpc, true);
scene_manager_stop(ibutton->scene_manager);
view_dispatcher_stop(ibutton->view_dispatcher);

Expand Down
34 changes: 24 additions & 10 deletions applications/main/infrared/infrared_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,30 +44,42 @@ static void infrared_tick_event_callback(void* context) {
scene_manager_handle_tick_event(infrared->scene_manager);
}

static void infrared_rpc_command_callback(RpcAppSystemEvent event, void* context) {
static void infrared_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
furi_assert(context);
InfraredApp* infrared = context;
furi_assert(infrared->rpc_ctx);

if(event == RpcAppEventSessionClose) {
if(event->type == RpcAppEventTypeSessionClose) {
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcSessionClose);
rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL);
infrared->rpc_ctx = NULL;
} else if(event == RpcAppEventAppExit) {
} else if(event->type == RpcAppEventTypeAppExit) {
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcExit);
} else if(event == RpcAppEventLoadFile) {
} else if(event->type == RpcAppEventTypeLoadFile) {
furi_assert(event->data.type == RpcAppSystemEventDataTypeString);
furi_string_set(infrared->file_path, event->data.string);
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcLoad);
} else if(event == RpcAppEventButtonPress) {
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPress);
} else if(event == RpcAppEventButtonRelease) {
infrared->view_dispatcher, InfraredCustomEventTypeRpcLoadFile);
} else if(event->type == RpcAppEventTypeButtonPress) {
furi_assert(
event->data.type == RpcAppSystemEventDataTypeString ||
event->data.type == RpcAppSystemEventDataTypeInt32);
if(event->data.type == RpcAppSystemEventDataTypeString) {
furi_string_set(infrared->button_name, event->data.string);
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressName);
} else {
infrared->app_state.current_button_index = event->data.i32;
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressIndex);
}
} else if(event->type == RpcAppEventTypeButtonRelease) {
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonRelease);
} else {
rpc_system_app_confirm(infrared->rpc_ctx, event, false);
rpc_system_app_confirm(infrared->rpc_ctx, false);
}
}

Expand Down Expand Up @@ -117,6 +129,7 @@ static InfraredApp* infrared_alloc() {
InfraredApp* infrared = malloc(sizeof(InfraredApp));

infrared->file_path = furi_string_alloc();
infrared->button_name = furi_string_alloc();

InfraredAppState* app_state = &infrared->app_state;
app_state->is_learning_new_remote = false;
Expand Down Expand Up @@ -247,6 +260,7 @@ static void infrared_free(InfraredApp* infrared) {
infrared->gui = NULL;

furi_string_free(infrared->file_path);
furi_string_free(infrared->button_name);

free(infrared);
}
Expand Down
1 change: 1 addition & 0 deletions applications/main/infrared/infrared_app_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ struct InfraredApp {
InfraredProgressView* progress; /**< Custom view for showing brute force progress. */

FuriString* file_path; /**< Full path to the currently loaded file. */
FuriString* button_name; /** Name of the button requested in RPC mode. */
/** Arbitrary text storage for various inputs. */
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
InfraredAppState app_state; /**< Application state. */
Expand Down
2 changes: 1 addition & 1 deletion applications/main/infrared/infrared_brute_force.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) {

bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
furi_assert(brute_force->is_started);
const bool success = infrared_signal_search_and_read(
const bool success = infrared_signal_search_by_name_and_read(
brute_force->current_signal,
brute_force->ff,
furi_string_get_cstr(brute_force->current_record_name));
Expand Down
5 changes: 3 additions & 2 deletions applications/main/infrared/infrared_custom_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ enum InfraredCustomEventType {
InfraredCustomEventTypeButtonSelected,
InfraredCustomEventTypeBackPressed,

InfraredCustomEventTypeRpcLoad,
InfraredCustomEventTypeRpcLoadFile,
InfraredCustomEventTypeRpcExit,
InfraredCustomEventTypeRpcButtonPress,
InfraredCustomEventTypeRpcButtonPressName,
InfraredCustomEventTypeRpcButtonPressIndex,
InfraredCustomEventTypeRpcButtonRelease,
InfraredCustomEventTypeRpcSessionClose,
};
Expand Down
7 changes: 3 additions & 4 deletions applications/main/infrared/infrared_remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,9 @@ bool infrared_remote_load_signal(
const char* path = furi_string_get_cstr(remote->path);
if(!flipper_format_buffered_file_open_existing(ff, path)) break;

const char* name = infrared_remote_get_signal_name(remote, index);

if(!infrared_signal_search_and_read(signal, ff, name)) {
FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", name, path);
if(!infrared_signal_search_by_index_and_read(signal, ff, index)) {
const char* signal_name = infrared_remote_get_signal_name(remote, index);
FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", signal_name, path);
break;
}

Expand Down
36 changes: 27 additions & 9 deletions applications/main/infrared/infrared_signal.c
Original file line number Diff line number Diff line change
Expand Up @@ -266,19 +266,37 @@ bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name) {
return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name);
}

bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name) {
bool infrared_signal_search_by_name_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
const char* name) {
bool success = false;
FuriString* tmp = furi_string_alloc();

do {
bool is_name_found = false;
while(!is_name_found && infrared_signal_read_name(ff, tmp)) { //-V560
is_name_found = furi_string_equal(tmp, name);
while(infrared_signal_read_name(ff, tmp)) {
if(furi_string_equal(tmp, name)) {
success = infrared_signal_read_body(signal, ff);
break;
}
if(!is_name_found) break; //-V547
if(!infrared_signal_read_body(signal, ff)) break; //-V779
success = true;
} while(false);
}

furi_string_free(tmp);
return success;
}

bool infrared_signal_search_by_index_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
size_t index) {
bool success = false;
FuriString* tmp = furi_string_alloc();

for(uint32_t i = 0; infrared_signal_read_name(ff, tmp); ++i) {
if(i == index) {
success = infrared_signal_read_body(signal, ff);
break;
}
}

furi_string_free(tmp);
return success;
Expand Down
21 changes: 20 additions & 1 deletion applications/main/infrared/infrared_signal.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,26 @@ bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name);
* @param[in] name pointer to a zero-terminated string containing the requested signal name.
* @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found).
*/
bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name);
bool infrared_signal_search_by_name_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
const char* name);

/**
* @brief Read a signal with a particular index from a FlipperFormat file into an InfraredSignal instance.
*
* This function will look for a signal with the given index and if found, attempt to read it.
* Same considerations apply as to infrared_signal_read().
*
* @param[in,out] signal pointer to the instance to be read into.
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
* @param[in] index the requested signal index.
* @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found).
*/
bool infrared_signal_search_by_index_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
size_t index);

/**
* @brief Save a signal contained in an InfraredSignal instance to a FlipperFormat file.
Expand Down
Loading

0 comments on commit 49dcf81

Please sign in to comment.