Skip to content

Commit

Permalink
Merge branch 'dev' into raed_skylanders_faster
Browse files Browse the repository at this point in the history
  • Loading branch information
bettse authored Dec 19, 2024
2 parents 0da92fe + a7b3a13 commit 8cd202b
Show file tree
Hide file tree
Showing 29 changed files with 536 additions and 46 deletions.
108 changes: 108 additions & 0 deletions applications/debug/unit_tests/tests/furi/furi_stdio_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#include <furi.h>
#include <errno.h>
#include <stdio.h>
#include "../test.h" // IWYU pragma: keep

#define TAG "StdioTest"

#define CONTEXT_MAGIC ((void*)0xDEADBEEF)

// stdin

static char mock_in[256];
static size_t mock_in_len, mock_in_pos;

static void set_mock_in(const char* str) {
size_t len = strlen(str);
strcpy(mock_in, str);
mock_in_len = len;
mock_in_pos = 0;
}

static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context) {
UNUSED(wait);
furi_check(context == CONTEXT_MAGIC);
size_t remaining = mock_in_len - mock_in_pos;
size = MIN(remaining, size);
memcpy(buffer, mock_in + mock_in_pos, size);
mock_in_pos += size;
return size;
}

void test_stdin(void) {
FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback();
furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC);
char buf[256];

// plain in
set_mock_in("Hello, World!\n");
fgets(buf, sizeof(buf), stdin);
mu_assert_string_eq("Hello, World!\n", buf);
mu_assert_int_eq(EOF, getchar());

// ungetc
ungetc('i', stdin);
ungetc('H', stdin);
fgets(buf, sizeof(buf), stdin);
mu_assert_string_eq("Hi", buf);
mu_assert_int_eq(EOF, getchar());

// ungetc + plain in
set_mock_in(" World");
ungetc('i', stdin);
ungetc('H', stdin);
fgets(buf, sizeof(buf), stdin);
mu_assert_string_eq("Hi World", buf);
mu_assert_int_eq(EOF, getchar());

// partial plain in
set_mock_in("Hello, World!\n");
fgets(buf, strlen("Hello") + 1, stdin);
mu_assert_string_eq("Hello", buf);
mu_assert_int_eq(',', getchar());
fgets(buf, sizeof(buf), stdin);
mu_assert_string_eq(" World!\n", buf);

furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC);
}

// stdout

static FuriString* mock_out;
FuriThreadStdoutWriteCallback original_out_cb;

static void mock_out_cb(const char* data, size_t size, void* context) {
furi_check(context == CONTEXT_MAGIC);
// there's no furi_string_cat_strn :(
for(size_t i = 0; i < size; i++) {
furi_string_push_back(mock_out, data[i]);
}
}

static void assert_and_clear_mock_out(const char* expected) {
// return the original stdout callback for the duration of the check
// if the check fails, we don't want the error to end up in our buffer,
// we want to be able to see it!
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
mu_assert_string_eq(expected, furi_string_get_cstr(mock_out));
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);

furi_string_reset(mock_out);
}

void test_stdout(void) {
original_out_cb = furi_thread_get_stdout_callback();
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);
mock_out = furi_string_alloc();

puts("Hello, World!");
assert_and_clear_mock_out("Hello, World!\n");

printf("He");
printf("llo!");
fflush(stdout);
assert_and_clear_mock_out("Hello!");

furi_string_free(mock_out);
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
}
8 changes: 8 additions & 0 deletions applications/debug/unit_tests/tests/furi/furi_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ void test_furi_memmgr(void);
void test_furi_event_loop(void);
void test_errno_saving(void);
void test_furi_primitives(void);
void test_stdin(void);
void test_stdout(void);

static int foo = 0;

Expand Down Expand Up @@ -52,6 +54,11 @@ MU_TEST(mu_test_furi_primitives) {
test_furi_primitives();
}

MU_TEST(mu_test_stdio) {
test_stdin();
test_stdout();
}

MU_TEST_SUITE(test_suite) {
MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
MU_RUN_TEST(test_check);
Expand All @@ -61,6 +68,7 @@ MU_TEST_SUITE(test_suite) {
MU_RUN_TEST(mu_test_furi_pubsub);
MU_RUN_TEST(mu_test_furi_memmgr);
MU_RUN_TEST(mu_test_furi_event_loop);
MU_RUN_TEST(mu_test_stdio);
MU_RUN_TEST(mu_test_errno_saving);
MU_RUN_TEST(mu_test_furi_primitives);
}
Expand Down
33 changes: 33 additions & 0 deletions applications/main/infrared/infrared_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ static void infrared_rpc_command_callback(const RpcAppSystemEvent* event, void*
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressIndex);
}
} else if(event->type == RpcAppEventTypeButtonPressRelease) {
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, InfraredCustomEventTypeRpcButtonPressReleaseName);
} else {
infrared->app_state.current_button_index = event->data.i32;
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressReleaseIndex);
}
} else if(event->type == RpcAppEventTypeButtonRelease) {
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonRelease);
Expand Down Expand Up @@ -411,6 +424,26 @@ void infrared_tx_stop(InfraredApp* infrared) {
infrared->app_state.last_transmit_time = furi_get_tick();
}

void infrared_tx_send_once(InfraredApp* infrared) {
if(infrared->app_state.is_transmitting) {
return;
}

dolphin_deed(DolphinDeedIrSend);
infrared_signal_transmit(infrared->current_signal);
}

InfraredErrorCode infrared_tx_send_once_button_index(InfraredApp* infrared, size_t button_index) {
furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote));

InfraredErrorCode error = infrared_remote_load_signal(
infrared->remote, infrared->current_signal, infrared->app_state.current_button_index);
if(!INFRARED_ERROR_PRESENT(error)) {
infrared_tx_send_once(infrared);
}

return error;
}
void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) {
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewLoading);
furi_thread_set_callback(infrared->task_thread, callback);
Expand Down
14 changes: 14 additions & 0 deletions applications/main/infrared/infrared_app_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,20 @@ InfraredErrorCode infrared_tx_start_button_index(InfraredApp* infrared, size_t b
*/
void infrared_tx_stop(InfraredApp* infrared);

/**
* @brief Transmit the currently loaded signal once.
*
* @param[in,out] infrared pointer to the application instance.
*/
void infrared_tx_send_once(InfraredApp* infrared);

/**
* @brief Load the signal under the given index and transmit it once.
*
* @param[in,out] infrared pointer to the application instance.
*/
InfraredErrorCode infrared_tx_send_once_button_index(InfraredApp* infrared, size_t button_index);

/**
* @brief Start a blocking task in a separate thread.
*
Expand Down
2 changes: 2 additions & 0 deletions applications/main/infrared/infrared_custom_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ enum InfraredCustomEventType {
InfraredCustomEventTypeRpcButtonPressName,
InfraredCustomEventTypeRpcButtonPressIndex,
InfraredCustomEventTypeRpcButtonRelease,
InfraredCustomEventTypeRpcButtonPressReleaseName,
InfraredCustomEventTypeRpcButtonPressReleaseIndex,
InfraredCustomEventTypeRpcSessionClose,

InfraredCustomEventTypeGpioTxPinChanged,
Expand Down
43 changes: 43 additions & 0 deletions applications/main/infrared/scenes/infrared_scene_rpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,49 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {

rpc_system_app_confirm(infrared->rpc_ctx, result);

} else if(
event.event == InfraredCustomEventTypeRpcButtonPressReleaseName ||
event.event == InfraredCustomEventTypeRpcButtonPressReleaseIndex) {
bool result = false;

// Send the signal once and stop
if(rpc_state == InfraredRpcStateLoaded) {
if(event.event == InfraredCustomEventTypeRpcButtonPressReleaseName) {
const char* button_name = furi_string_get_cstr(infrared->button_name);
size_t index;
const bool index_found =
infrared_remote_get_signal_index(infrared->remote, button_name, &index);
app_state->current_button_index = index_found ? (signed)index :
InfraredButtonIndexNone;
FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name);
} else {
FURI_LOG_D(
TAG, "Sending signal with index \"%ld\"", app_state->current_button_index);
}
if(infrared->app_state.current_button_index != InfraredButtonIndexNone) {
InfraredErrorCode error = infrared_tx_send_once_button_index(
infrared, app_state->current_button_index);
if(!INFRARED_ERROR_PRESENT(error)) {
const char* remote_name = infrared_remote_get_name(infrared->remote);
infrared_text_store_set(infrared, 0, "emulating\n%s", remote_name);

infrared_scene_rpc_show(infrared);
result = true;
} else {
rpc_system_app_set_error_code(
infrared->rpc_ctx, RpcAppSystemErrorCodeInternalParse);
rpc_system_app_set_error_text(
infrared->rpc_ctx, "Cannot load button data");
result = false;
}
}
}

if(result) {
scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
}
rpc_system_app_confirm(infrared->rpc_ctx, result);
} else if(
event.event == InfraredCustomEventTypeRpcExit ||
event.event == InfraredCustomEventTypeRpcSessionClose ||
Expand Down
1 change: 1 addition & 0 deletions applications/main/subghz/helpers/subghz_custom_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ typedef enum {
SubGhzCustomEventSceneRpcLoad,
SubGhzCustomEventSceneRpcButtonPress,
SubGhzCustomEventSceneRpcButtonRelease,
SubGhzCustomEventSceneRpcButtonPressRelease,
SubGhzCustomEventSceneRpcSessionClose,

SubGhzCustomEventViewReceiverOK,
Expand Down
37 changes: 37 additions & 0 deletions applications/main/subghz/scenes/subghz_scene_rpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,43 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
rpc_system_app_confirm(subghz->rpc_ctx, result);
} else if(event.event == SubGhzCustomEventSceneRpcButtonPressRelease) {
bool result = false;
if(state == SubGhzRpcStateLoaded) {
switch(
subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) {
case SubGhzTxRxStartTxStateErrorOnlyRx:
rpc_system_app_set_error_code(
subghz->rpc_ctx, RpcAppSystemErrorCodeRegionLock);
rpc_system_app_set_error_text(
subghz->rpc_ctx,
"Transmission on this frequency is restricted in your region");
break;
case SubGhzTxRxStartTxStateErrorParserOthers:
rpc_system_app_set_error_code(
subghz->rpc_ctx, RpcAppSystemErrorCodeInternalParse);
rpc_system_app_set_error_text(
subghz->rpc_ctx, "Error in protocol parameters description");
break;

default: //if(SubGhzTxRxStartTxStateOk)
result = true;
subghz_blink_start(subghz);
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateTx);
break;
}
}

// Stop transmission
if(state == SubGhzRpcStateTx) {
subghz_txrx_stop(subghz->txrx);
subghz_blink_stop(subghz);
result = true;
}
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
rpc_system_app_confirm(subghz->rpc_ctx, result);
} else if(event.event == SubGhzCustomEventSceneRpcLoad) {
bool result = false;
if(state == SubGhzRpcStateIdle) {
Expand Down
3 changes: 3 additions & 0 deletions applications/main/subghz/subghz.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ static void subghz_rpc_command_callback(const RpcAppSystemEvent* event, void* co
} else if(event->type == RpcAppEventTypeButtonRelease) {
view_dispatcher_send_custom_event(
subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonRelease);
} else if(event->type == RpcAppEventTypeButtonPressRelease) {
view_dispatcher_send_custom_event(
subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonPressRelease);
} else {
rpc_system_app_confirm(subghz->rpc_ctx, false);
}
Expand Down
10 changes: 5 additions & 5 deletions applications/services/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,9 @@ void cli_session_open(Cli* cli, void* session) {
cli->session = session;
if(cli->session != NULL) {
cli->session->init();
furi_thread_set_stdout_callback(cli->session->tx_stdout);
furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL);
} else {
furi_thread_set_stdout_callback(NULL);
furi_thread_set_stdout_callback(NULL, NULL);
}
furi_semaphore_release(cli->idle_sem);
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
Expand All @@ -447,7 +447,7 @@ void cli_session_close(Cli* cli) {
cli->session->deinit();
}
cli->session = NULL;
furi_thread_set_stdout_callback(NULL);
furi_thread_set_stdout_callback(NULL, NULL);
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
}

Expand All @@ -461,9 +461,9 @@ int32_t cli_srv(void* p) {
furi_record_create(RECORD_CLI, cli);

if(cli->session != NULL) {
furi_thread_set_stdout_callback(cli->session->tx_stdout);
furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL);
} else {
furi_thread_set_stdout_callback(NULL);
furi_thread_set_stdout_callback(NULL, NULL);
}

if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) {
Expand Down
3 changes: 2 additions & 1 deletion applications/services/cli/cli_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ struct CliSession {
void (*init)(void);
void (*deinit)(void);
size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout);
size_t (*rx_stdin)(uint8_t* buffer, size_t size, uint32_t timeout, void* context);
void (*tx)(const uint8_t* buffer, size_t size);
void (*tx_stdout)(const char* data, size_t size);
void (*tx_stdout)(const char* data, size_t size, void* context);
bool (*is_connected)(void);
};

Expand Down
Loading

0 comments on commit 8cd202b

Please sign in to comment.