[FL-2568] Infrared C port (#1326)

* Add skeleton for infrared C port, rename old app
* Add scene stubs
* Add more views
* Misc changes
* Add remote and signal class stubs
* Complete infrared signal class
* Add remote button class stub
* Check if button contains a signal during destruction
* Complete infrared signal class more
* Implement remote storing
* Implement remote loading
* Fix error handling
* Implement remote transmitting
* Rename scene
* Canonise event consumption
* Implement remote learning (stub)
* Implement learn success screen (stub)
* Implement AskBack scene
* Improve remote saving&loading
* Fix remote file name
* Add LearnDone scene
* Switch from Remote scene correctly
* Add SceneButtonSelect
* Remove unneeded assert
* Add new SceneManager method
* Use new SceneManager method in Infrared
* Implement renaming of buttons and remotes
* Implement deleting of buttons and remotes
* Add universal remotes list
* Add brute force code
* Brute force code improvements
* Partially implement Universal Remote GUI
* Fix wrong singnal handling
* Fully implement Universal Remote
* Use standard custom events everywhere
* Return infrared CLI
* Remove old Infrared app
* Change container name
* Fix scene order
* Put ButtonPanel into stack only when needed
* Show loading animation during slow operations
* Do not hardcode Loading widget coordinates
* Switch Loading widget orientation as needed
* Save Start scene state
* Save Remote scene state
* Save Edit scene state
* Save EditButtonSelect scene state
* Do not use scene state
* Use string_t instead of const char* for brevity
* Fix memory leak
* Fix saving raw remotes
* Add Infrared debug menu
* Add debug view
* Move Infrared monitor into Infrared application
* Remove old Infrared monitor app
* Use common signal received callback everywhere
This commit is contained in:
Georgii Surkov 2022-06-21 15:45:50 +03:00 committed by GitHub
parent 839e52ac32
commit a8acfcabb4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 2946 additions and 3865 deletions

View file

@ -29,7 +29,6 @@ extern int32_t display_test_app(void* p);
extern int32_t gpio_app(void* p);
extern int32_t ibutton_app(void* p);
extern int32_t infrared_app(void* p);
extern int32_t infrared_monitor_app(void* p);
extern int32_t keypad_test_app(void* p);
extern int32_t lfrfid_app(void* p);
extern int32_t lfrfid_debug_app(void* p);
@ -412,14 +411,6 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = {
.flags = FlipperApplicationFlagDefault},
#endif
#ifdef APP_INFRARED_MONITOR
{.app = infrared_monitor_app,
.name = "Infrared Monitor",
.stack_size = 1024,
.icon = NULL,
.flags = FlipperApplicationFlagDefault},
#endif
#ifdef APP_LF_RFID
{.app = lfrfid_debug_app,
.name = "LF-RFID Debug",

View file

@ -51,7 +51,6 @@ APP_SNAKE_GAME = 1
# Debug
APP_ACCESSOR = 1
APP_BLINK = 1
APP_INFRARED_MONITOR = 1
APP_KEYPAD_TEST = 1
APP_SD_TEST = 1
APP_VIBRO_TEST = 1
@ -70,14 +69,6 @@ endif
# that will be shown in menu
# Prefix with APP_*
APP_INFRARED_MONITOR ?= 0
ifeq ($(APP_INFRARED_MONITOR), 1)
CFLAGS += -DAPP_INFRARED_MONITOR
SRV_GUI = 1
endif
APP_UNIT_TESTS ?= 0
ifeq ($(APP_UNIT_TESTS), 1)
CFLAGS += -DAPP_UNIT_TESTS

View file

@ -20,10 +20,10 @@ typedef struct {
static void loading_draw_callback(Canvas* canvas, void* _model) {
LoadingModel* model = (LoadingModel*)_model;
uint8_t x = 7;
uint8_t y = 40;
uint8_t width = 49;
uint8_t height = 47;
uint8_t x = (canvas_width(canvas) - width) / 2;
uint8_t y = (canvas_height(canvas) - height) / 2;
elements_bold_rounded_frame(canvas, x, y, width, height);

View file

@ -165,6 +165,25 @@ bool scene_manager_search_and_switch_to_previous_scene(
}
}
bool scene_manager_search_and_switch_to_previous_scene_one_of(
SceneManager* scene_manager,
const uint32_t* scene_ids,
size_t scene_ids_size) {
furi_assert(scene_manager);
furi_assert(scene_ids);
bool scene_found = false;
for(size_t i = 0; i < scene_ids_size; ++i) {
const uint32_t scene_id = scene_ids[i];
if(scene_manager_has_previous_scene(scene_manager, scene_id)) {
scene_manager_search_and_switch_to_previous_scene(scene_manager, scene_id);
scene_found = true;
break;
}
}
return scene_found;
}
bool scene_manager_has_previous_scene(SceneManager* scene_manager, uint32_t scene_id) {
furi_assert(scene_manager);
bool scene_found = false;

View file

@ -5,6 +5,7 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
@ -146,6 +147,19 @@ bool scene_manager_search_and_switch_to_previous_scene(
SceneManager* scene_manager,
uint32_t scene_id);
/** Search and switch to previous Scene, multiple choice
*
* @param scene_manager SceneManager instance
* @param scene_ids Array of scene IDs
* @param scene_ids_size Array of scene IDs size
*
* @return true if one of previous scenes was found, false otherwise
*/
bool scene_manager_search_and_switch_to_previous_scene_one_of(
SceneManager* scene_manager,
const uint32_t* scene_ids,
size_t scene_ids_size);
/** Clear Scene stack and switch to another Scene
*
* @param scene_manager SceneManager instance

View file

@ -1,157 +0,0 @@
#include "../infrared_app_signal.h"
#include "infrared.h"
#include "infrared/helpers/infrared_parser.h"
#include "infrared_worker.h"
#include "m-string.h"
#include <flipper_format/flipper_format.h>
#include <memory>
#include <string>
#include <furi_hal_infrared.h>
#define TAG "InfraredParser"
bool infrared_parser_save_signal(
FlipperFormat* ff,
const InfraredAppSignal& signal,
const std::string& name) {
furi_assert(ff);
furi_assert(!name.empty());
bool result = false;
do {
if(!flipper_format_write_comment_cstr(ff, "")) break;
if(!flipper_format_write_string_cstr(ff, "name", name.c_str())) break;
if(signal.is_raw()) {
furi_assert(signal.get_raw_signal().timings_cnt <= MAX_TIMINGS_AMOUNT);
auto raw_signal = signal.get_raw_signal();
if(!flipper_format_write_string_cstr(ff, "type", "raw")) break;
if(!flipper_format_write_uint32(ff, "frequency", &raw_signal.frequency, 1)) break;
if(!flipper_format_write_float(ff, "duty_cycle", &raw_signal.duty_cycle, 1)) break;
if(!flipper_format_write_uint32(ff, "data", raw_signal.timings, raw_signal.timings_cnt))
break;
} else {
auto parsed_signal = signal.get_message();
const char* protocol_name = infrared_get_protocol_name(parsed_signal.protocol);
if(!flipper_format_write_string_cstr(ff, "type", "parsed")) break;
if(!flipper_format_write_string_cstr(ff, "protocol", protocol_name)) break;
if(!flipper_format_write_hex(ff, "address", (uint8_t*)&parsed_signal.address, 4))
break;
if(!flipper_format_write_hex(ff, "command", (uint8_t*)&parsed_signal.command, 4))
break;
}
result = true;
} while(0);
return result;
}
bool infrared_parser_read_signal(FlipperFormat* ff, InfraredAppSignal& signal, std::string& name) {
furi_assert(ff);
bool result = false;
string_t read_string;
string_init(read_string);
do {
if(!flipper_format_read_string(ff, "name", read_string)) break;
name = string_get_cstr(read_string);
if(!flipper_format_read_string(ff, "type", read_string)) break;
if(!string_cmp_str(read_string, "raw")) {
uint32_t* timings = nullptr;
uint32_t timings_cnt = 0;
uint32_t frequency = 0;
float duty_cycle = 0;
if(!flipper_format_read_uint32(ff, "frequency", &frequency, 1)) break;
if(!flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1)) break;
if(!flipper_format_get_value_count(ff, "data", &timings_cnt)) break;
if(timings_cnt > MAX_TIMINGS_AMOUNT) break;
timings = (uint32_t*)malloc(sizeof(uint32_t) * timings_cnt);
if(flipper_format_read_uint32(ff, "data", timings, timings_cnt)) {
signal.set_raw_signal(timings, timings_cnt, frequency, duty_cycle);
result = true;
}
free(timings);
} else if(!string_cmp_str(read_string, "parsed")) {
InfraredMessage parsed_signal;
if(!flipper_format_read_string(ff, "protocol", read_string)) break;
parsed_signal.protocol = infrared_get_protocol_by_name(string_get_cstr(read_string));
if(!flipper_format_read_hex(ff, "address", (uint8_t*)&parsed_signal.address, 4)) break;
if(!flipper_format_read_hex(ff, "command", (uint8_t*)&parsed_signal.command, 4)) break;
if(!infrared_parser_is_parsed_signal_valid(&parsed_signal)) break;
signal.set_message(&parsed_signal);
result = true;
} else {
FURI_LOG_E(TAG, "Unknown type of signal (allowed - raw/parsed) ");
}
} while(0);
string_clear(read_string);
return result;
}
bool infrared_parser_is_parsed_signal_valid(const InfraredMessage* signal) {
furi_assert(signal);
bool result = true;
if(!infrared_is_protocol_valid(signal->protocol)) {
FURI_LOG_E(TAG, "Unknown protocol");
result = false;
}
if(result) {
uint32_t address_length = infrared_get_protocol_address_length(signal->protocol);
uint32_t address_mask = (1LU << address_length) - 1;
if(signal->address != (signal->address & address_mask)) {
FURI_LOG_E(
TAG,
"Address is out of range (mask 0x%08lX): 0x%lX\r\n",
address_mask,
signal->address);
result = false;
}
}
if(result) {
uint32_t command_length = infrared_get_protocol_command_length(signal->protocol);
uint32_t command_mask = (1LU << command_length) - 1;
if(signal->command != (signal->command & command_mask)) {
FURI_LOG_E(
TAG,
"Command is out of range (mask 0x%08lX): 0x%lX\r\n",
command_mask,
signal->command);
result = false;
}
}
return result;
}
bool infrared_parser_is_raw_signal_valid(
uint32_t frequency,
float duty_cycle,
uint32_t timings_cnt) {
bool result = true;
if((frequency > INFRARED_MAX_FREQUENCY) || (frequency < INFRARED_MIN_FREQUENCY)) {
FURI_LOG_E(
TAG,
"Frequency is out of range (%lX - %lX): %lX",
INFRARED_MIN_FREQUENCY,
INFRARED_MAX_FREQUENCY,
frequency);
result = false;
} else if((duty_cycle <= 0) || (duty_cycle > 1)) {
FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)duty_cycle);
result = false;
} else if((timings_cnt <= 0) || (timings_cnt > MAX_TIMINGS_AMOUNT)) {
FURI_LOG_E(
TAG, "Timings amount is out of range (0 - %lX): %lX", MAX_TIMINGS_AMOUNT, timings_cnt);
result = false;
}
return result;
}

View file

@ -1,48 +0,0 @@
/**
* @file infrared_parser.h
* Infrared: Helper file for conversion Flipper File Format
* to Infrared signal class, and backwards
*/
#pragma once
#include "../infrared_app_signal.h"
#include <flipper_format/flipper_format.h>
#include <string>
/** Save Infrared signal into file
*
* @param ff - Flipper File Format instance
* @param signal - Infrared signal to save
* @param name - name for saved signal. Every
* signal on disk has name.
*/
bool infrared_parser_save_signal(
FlipperFormat* ff,
const InfraredAppSignal& signal,
const std::string& name);
/** Read Infrared signal from file
*
* @param ff - Flipper File Format instance
* @param signal - Infrared signal to read to
* @param name - name for saved signal. Every
* signal in file has name.
*/
bool infrared_parser_read_signal(FlipperFormat* ff, InfraredAppSignal& signal, std::string& name);
/** Validate parsed signal
*
* @signal - signal to validate
* @retval true if valid, false otherwise
*/
bool infrared_parser_is_parsed_signal_valid(const InfraredMessage* signal);
/** Validate raw signal
*
* @signal - signal to validate
* @retval true if valid, false otherwise
*/
bool infrared_parser_is_raw_signal_valid(
uint32_t frequency,
float duty_cycle,
uint32_t timings_cnt);

View file

@ -0,0 +1,397 @@
#include "infrared_i.h"
#include <string.h>
#include <dolphin/dolphin.h>
static const NotificationSequence* infrared_notification_sequences[] = {
&sequence_success,
&sequence_set_only_green_255,
&sequence_reset_green,
&sequence_blink_cyan_10,
&sequence_blink_magenta_10};
static void infrared_make_app_folder(Infrared* infrared) {
if(!storage_simply_mkdir(infrared->storage, INFRARED_APP_FOLDER)) {
dialog_message_show_storage_error(infrared->dialogs, "Cannot create\napp folder");
}
}
static bool infrared_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
Infrared* infrared = context;
return scene_manager_handle_custom_event(infrared->scene_manager, event);
}
static bool infrared_back_event_callback(void* context) {
furi_assert(context);
Infrared* infrared = context;
return scene_manager_handle_back_event(infrared->scene_manager);
}
static void infrared_tick_event_callback(void* context) {
furi_assert(context);
Infrared* infrared = context;
scene_manager_handle_tick_event(infrared->scene_manager);
}
static void infrared_find_vacant_remote_name(string_t name, const char* path) {
Storage* storage = furi_record_open("storage");
string_t base_path;
string_init_set_str(base_path, path);
if(string_end_with_str_p(base_path, INFRARED_APP_EXTENSION)) {
size_t filename_start = string_search_rchar(base_path, '/');
string_left(base_path, filename_start);
}
string_printf(base_path, "%s/%s%s", path, string_get_cstr(name), INFRARED_APP_EXTENSION);
FS_Error status = storage_common_stat(storage, string_get_cstr(base_path), NULL);
if(status == FSE_OK) {
/* If the suggested name is occupied, try another one (name2, name3, etc) */
size_t dot = string_search_rchar(base_path, '.');
string_left(base_path, dot);
string_t path_temp;
string_init(path_temp);
uint32_t i = 1;
do {
string_printf(
path_temp, "%s%u%s", string_get_cstr(base_path), ++i, INFRARED_APP_EXTENSION);
status = storage_common_stat(storage, string_get_cstr(path_temp), NULL);
} while(status == FSE_OK);
string_clear(path_temp);
if(status == FSE_NOT_EXIST) {
string_cat_printf(name, "%u", i);
}
}
string_clear(base_path);
furi_record_close("storage");
}
static Infrared* infrared_alloc() {
Infrared* infrared = malloc(sizeof(Infrared));
string_init(infrared->file_path);
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->edit_target = InfraredEditTargetNone;
app_state->edit_mode = InfraredEditModeNone;
app_state->current_button_index = InfraredButtonIndexNone;
infrared->scene_manager = scene_manager_alloc(&infrared_scene_handlers, infrared);
infrared->view_dispatcher = view_dispatcher_alloc();
infrared->gui = furi_record_open("gui");
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
view_dispatcher_attach_to_gui(view_dispatcher, infrared->gui, ViewDispatcherTypeFullscreen);
view_dispatcher_enable_queue(view_dispatcher);
view_dispatcher_set_event_callback_context(view_dispatcher, infrared);
view_dispatcher_set_custom_event_callback(view_dispatcher, infrared_custom_event_callback);
view_dispatcher_set_navigation_event_callback(view_dispatcher, infrared_back_event_callback);
view_dispatcher_set_tick_event_callback(view_dispatcher, infrared_tick_event_callback, 100);
infrared->storage = furi_record_open("storage");
infrared->dialogs = furi_record_open("dialogs");
infrared->notifications = furi_record_open("notification");
infrared->worker = infrared_worker_alloc();
infrared->remote = infrared_remote_alloc();
infrared->received_signal = infrared_signal_alloc();
infrared->brute_force = infrared_brute_force_alloc();
infrared->submenu = submenu_alloc();
view_dispatcher_add_view(
view_dispatcher, InfraredViewSubmenu, submenu_get_view(infrared->submenu));
infrared->text_input = text_input_alloc();
view_dispatcher_add_view(
view_dispatcher, InfraredViewTextInput, text_input_get_view(infrared->text_input));
infrared->dialog_ex = dialog_ex_alloc();
view_dispatcher_add_view(
view_dispatcher, InfraredViewDialogEx, dialog_ex_get_view(infrared->dialog_ex));
infrared->button_menu = button_menu_alloc();
view_dispatcher_add_view(
view_dispatcher, InfraredViewButtonMenu, button_menu_get_view(infrared->button_menu));
infrared->popup = popup_alloc();
view_dispatcher_add_view(view_dispatcher, InfraredViewPopup, popup_get_view(infrared->popup));
infrared->view_stack = view_stack_alloc();
view_dispatcher_add_view(
view_dispatcher, InfraredViewStack, view_stack_get_view(infrared->view_stack));
if(app_state->is_debug_enabled) {
infrared->debug_view = infrared_debug_view_alloc();
view_dispatcher_add_view(
view_dispatcher,
InfraredViewDebugView,
infrared_debug_view_get_view(infrared->debug_view));
}
infrared->button_panel = button_panel_alloc();
infrared->loading = loading_alloc();
infrared->progress = infrared_progress_view_alloc();
return infrared;
}
static void infrared_free(Infrared* infrared) {
furi_assert(infrared);
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
InfraredAppState* app_state = &infrared->app_state;
view_dispatcher_remove_view(view_dispatcher, InfraredViewSubmenu);
submenu_free(infrared->submenu);
view_dispatcher_remove_view(view_dispatcher, InfraredViewTextInput);
text_input_free(infrared->text_input);
view_dispatcher_remove_view(view_dispatcher, InfraredViewDialogEx);
dialog_ex_free(infrared->dialog_ex);
view_dispatcher_remove_view(view_dispatcher, InfraredViewButtonMenu);
button_menu_free(infrared->button_menu);
view_dispatcher_remove_view(view_dispatcher, InfraredViewPopup);
popup_free(infrared->popup);
view_dispatcher_remove_view(view_dispatcher, InfraredViewStack);
view_stack_free(infrared->view_stack);
if(app_state->is_debug_enabled) {
view_dispatcher_remove_view(view_dispatcher, InfraredViewDebugView);
infrared_debug_view_free(infrared->debug_view);
}
button_panel_free(infrared->button_panel);
loading_free(infrared->loading);
infrared_progress_view_free(infrared->progress);
view_dispatcher_free(view_dispatcher);
scene_manager_free(infrared->scene_manager);
infrared_brute_force_free(infrared->brute_force);
infrared_signal_free(infrared->received_signal);
infrared_remote_free(infrared->remote);
infrared_worker_free(infrared->worker);
furi_record_close("gui");
infrared->gui = NULL;
furi_record_close("notification");
infrared->notifications = NULL;
furi_record_close("dialogs");
infrared->dialogs = NULL;
furi_record_close("gui");
infrared->gui = NULL;
string_clear(infrared->file_path);
free(infrared);
}
bool infrared_add_remote_with_button(
Infrared* infrared,
const char* button_name,
InfraredSignal* signal) {
InfraredRemote* remote = infrared->remote;
string_t new_name, new_path;
string_init_set_str(new_name, INFRARED_DEFAULT_REMOTE_NAME);
string_init_set_str(new_path, INFRARED_APP_FOLDER);
infrared_find_vacant_remote_name(new_name, string_get_cstr(new_path));
string_cat_printf(new_path, "/%s%s", string_get_cstr(new_name), INFRARED_APP_EXTENSION);
infrared_remote_reset(remote);
infrared_remote_set_name(remote, string_get_cstr(new_name));
infrared_remote_set_path(remote, string_get_cstr(new_path));
string_clear(new_name);
string_clear(new_path);
return infrared_remote_add_button(remote, button_name, signal);
}
bool infrared_rename_current_remote(Infrared* infrared, const char* name) {
InfraredRemote* remote = infrared->remote;
const char* remote_path = infrared_remote_get_path(remote);
if(!strcmp(infrared_remote_get_name(remote), name)) {
return true;
}
string_t new_name;
string_init_set_str(new_name, name);
infrared_find_vacant_remote_name(new_name, remote_path);
string_t new_path;
string_init_set(new_path, infrared_remote_get_path(remote));
if(string_end_with_str_p(new_path, INFRARED_APP_EXTENSION)) {
size_t filename_start = string_search_rchar(new_path, '/');
string_left(new_path, filename_start);
}
string_cat_printf(new_path, "/%s%s", string_get_cstr(new_name), INFRARED_APP_EXTENSION);
Storage* storage = furi_record_open("storage");
FS_Error status = storage_common_rename(
storage, infrared_remote_get_path(remote), string_get_cstr(new_path));
infrared_remote_set_name(remote, string_get_cstr(new_name));
string_clear(new_name);
string_clear(new_path);
furi_record_close("storage");
return (status == FSE_OK || status == FSE_EXIST);
}
void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
if(infrared_signal_is_raw(signal)) {
InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
infrared_worker_set_raw_signal(infrared->worker, raw->timings, raw->timings_size);
} else {
InfraredMessage* message = infrared_signal_get_message(signal);
infrared_worker_set_decoded_signal(infrared->worker, message);
}
DOLPHIN_DEED(DolphinDeedIrSend);
infrared_worker_tx_start(infrared->worker);
}
void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
furi_assert(button_index < infrared_remote_get_button_count(infrared->remote));
InfraredRemoteButton* button = infrared_remote_get_button(infrared->remote, button_index);
InfraredSignal* signal = infrared_remote_button_get_signal(button);
infrared_tx_start_signal(infrared, signal);
}
void infrared_tx_start_received(Infrared* infrared) {
infrared_tx_start_signal(infrared, infrared->received_signal);
}
void infrared_tx_stop(Infrared* infrared) {
infrared_worker_tx_stop(infrared->worker);
}
void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) {
va_list args;
va_start(args, text);
vsnprintf(infrared->text_store[bank], INFRARED_TEXT_STORE_SIZE, text, args);
va_end(args);
}
void infrared_text_store_clear(Infrared* infrared, uint32_t bank) {
memset(infrared->text_store[bank], 0, INFRARED_TEXT_STORE_SIZE);
}
void infrared_play_notification_message(Infrared* infrared, uint32_t message) {
furi_assert(message < sizeof(infrared_notification_sequences) / sizeof(NotificationSequence*));
notification_message(infrared->notifications, infrared_notification_sequences[message]);
}
void infrared_show_loading_popup(Infrared* infrared, bool show) {
TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
ViewStack* view_stack = infrared->view_stack;
Loading* loading = infrared->loading;
if(show) {
// Raise timer priority so that animations can play
vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
view_stack_add_view(view_stack, loading_get_view(loading));
} else {
view_stack_remove_view(view_stack, loading_get_view(loading));
// Restore default timer priority
vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
}
}
void infrared_signal_sent_callback(void* context) {
furi_assert(context);
Infrared* infrared = context;
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkSend);
}
void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
furi_assert(context);
Infrared* infrared = context;
if(infrared_worker_signal_is_decoded(received_signal)) {
infrared_signal_set_message(
infrared->received_signal, infrared_worker_get_decoded_signal(received_signal));
} else {
const uint32_t* timings;
size_t timings_size;
infrared_worker_get_raw_signal(received_signal, &timings, &timings_size);
infrared_signal_set_raw_signal(
infrared->received_signal,
timings,
timings_size,
INFRARED_COMMON_CARRIER_FREQUENCY,
INFRARED_COMMON_DUTY_CYCLE);
}
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeSignalReceived);
}
void infrared_text_input_callback(void* context) {
furi_assert(context);
Infrared* infrared = context;
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeTextEditDone);
}
void infrared_popup_timeout_callback(void* context) {
furi_assert(context);
Infrared* infrared = context;
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypePopupTimeout);
}
int32_t infrared_app(void* p) {
Infrared* infrared = infrared_alloc();
infrared_make_app_folder(infrared);
bool is_remote_loaded = false;
if(p) {
string_set_str(infrared->file_path, (const char*)p);
is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path);
if(!is_remote_loaded) {
dialog_message_show_storage_error(
infrared->dialogs, "Failed to load\nselected remote");
return -1;
}
}
if(is_remote_loaded) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
} else {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneStart);
}
view_dispatcher_run(infrared->view_dispatcher);
infrared_free(infrared);
return 0;
}

View file

@ -0,0 +1,3 @@
#pragma once
typedef struct Infrared Infrared;

View file

@ -1,250 +0,0 @@
#include "infrared_app.h"
#include "m-string.h"
#include <infrared_worker.h>
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdio.h>
#include <callback-connector.h>
int32_t InfraredApp::run(void* args) {
InfraredAppEvent event;
bool consumed;
bool exit = false;
if(args) {
string_t path;
string_init_set_str(path, (char*)args);
if(string_end_with_str_p(path, InfraredApp::infrared_extension)) {
bool result = remote_manager.load(path);
if(result) {
current_scene = InfraredApp::Scene::Remote;
} else {
printf("Failed to load remote \'%s\'\r\n", string_get_cstr(path));
return -1;
}
}
string_clear(path);
}
scenes[current_scene]->on_enter(this);
while(!exit) {
view_manager.receive_event(&event);
if(event.type == InfraredAppEvent::Type::Exit) break;
consumed = scenes[current_scene]->on_event(this, &event);
if(!consumed) {
if(event.type == InfraredAppEvent::Type::Back) {
exit = switch_to_previous_scene();
}
}
};
scenes[current_scene]->on_exit(this);
return 0;
};
InfraredApp::InfraredApp() {
furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size());
string_init_set_str(file_path, InfraredApp::infrared_directory);
notification = static_cast<NotificationApp*>(furi_record_open("notification"));
dialogs = static_cast<DialogsApp*>(furi_record_open("dialogs"));
infrared_worker = infrared_worker_alloc();
}
InfraredApp::~InfraredApp() {
infrared_worker_free(infrared_worker);
furi_record_close("notification");
furi_record_close("dialogs");
string_clear(file_path);
for(auto& [key, scene] : scenes) delete scene;
}
InfraredAppViewManager* InfraredApp::get_view_manager() {
return &view_manager;
}
void InfraredApp::set_learn_new_remote(bool value) {
learn_new_remote = value;
}
bool InfraredApp::get_learn_new_remote() {
return learn_new_remote;
}
void InfraredApp::switch_to_next_scene(Scene next_scene) {
previous_scenes_list.push_front(current_scene);
switch_to_next_scene_without_saving(next_scene);
}
void InfraredApp::switch_to_next_scene_without_saving(Scene next_scene) {
if(next_scene != Scene::Exit) {
scenes[current_scene]->on_exit(this);
current_scene = next_scene;
scenes[current_scene]->on_enter(this);
view_manager.clear_events();
}
}
void InfraredApp::search_and_switch_to_previous_scene(
const std::initializer_list<Scene>& scenes_list) {
Scene previous_scene = Scene::Start;
bool scene_found = false;
while(!scene_found) {
previous_scene = get_previous_scene();
if(previous_scene == Scene::Exit) break;
for(Scene element : scenes_list) {
if(previous_scene == element) {
scene_found = true;
break;
}
}
}
if(previous_scene == Scene::Exit) {
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::Exit;
view_manager.send_event(&event);
} else {
scenes[current_scene]->on_exit(this);
current_scene = previous_scene;
scenes[current_scene]->on_enter(this);
view_manager.clear_events();
}
}
bool InfraredApp::switch_to_previous_scene(uint8_t count) {
Scene previous_scene = Scene::Start;
for(uint8_t i = 0; i < count; i++) previous_scene = get_previous_scene();
if(previous_scene == Scene::Exit) return true;
scenes[current_scene]->on_exit(this);
current_scene = previous_scene;
scenes[current_scene]->on_enter(this);
view_manager.clear_events();
return false;
}
InfraredApp::Scene InfraredApp::get_previous_scene() {
Scene scene = Scene::Exit;
if(!previous_scenes_list.empty()) {
scene = previous_scenes_list.front();
previous_scenes_list.pop_front();
}
return scene;
}
InfraredAppRemoteManager* InfraredApp::get_remote_manager() {
return &remote_manager;
}
void InfraredApp::set_text_store(uint8_t index, const char* text...) {
furi_check(index < text_store_max);
va_list args;
va_start(args, text);
vsnprintf(text_store[index], text_store_size, text, args);
va_end(args);
}
char* InfraredApp::get_text_store(uint8_t index) {
furi_check(index < text_store_max);
return text_store[index];
}
uint8_t InfraredApp::get_text_store_size() {
return text_store_size;
}
void InfraredApp::text_input_callback(void* context) {
InfraredApp* app = static_cast<InfraredApp*>(context);
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::TextEditDone;
app->get_view_manager()->send_event(&event);
}
void InfraredApp::popup_callback(void* context) {
InfraredApp* app = static_cast<InfraredApp*>(context);
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::PopupTimer;
app->get_view_manager()->send_event(&event);
}
void InfraredApp::set_edit_element(InfraredApp::EditElement value) {
element = value;
}
InfraredApp::EditElement InfraredApp::get_edit_element(void) {
return element;
}
void InfraredApp::set_edit_action(InfraredApp::EditAction value) {
action = value;
}
InfraredApp::EditAction InfraredApp::get_edit_action(void) {
return action;
}
void InfraredApp::set_current_button(int value) {
current_button = value;
}
int InfraredApp::get_current_button() {
return current_button;
}
void InfraredApp::notify_success() {
notification_message(notification, &sequence_success);
}
void InfraredApp::notify_blink_read() {
notification_message(notification, &sequence_blink_cyan_10);
}
void InfraredApp::notify_blink_send() {
notification_message(notification, &sequence_blink_magenta_10);
}
DialogsApp* InfraredApp::get_dialogs() {
return dialogs;
}
void InfraredApp::notify_green_on() {
notification_message(notification, &sequence_set_only_green_255);
}
void InfraredApp::notify_green_off() {
notification_message(notification, &sequence_reset_green);
}
InfraredWorker* InfraredApp::get_infrared_worker() {
return infrared_worker;
}
const InfraredAppSignal& InfraredApp::get_received_signal() const {
return received_signal;
}
void InfraredApp::set_received_signal(const InfraredAppSignal& signal) {
received_signal = signal;
}
void InfraredApp::signal_sent_callback(void* context) {
InfraredApp* app = static_cast<InfraredApp*>(context);
app->notify_blink_send();
}

View file

@ -1,326 +0,0 @@
/**
* @file infrared_app.h
* Infrared: Main infrared application class
*/
#pragma once
#include <map>
#include <infrared.h>
#include <furi.h>
#include <forward_list>
#include <stdint.h>
#include <notification/notification_messages.h>
#include <dialogs/dialogs.h>
#include <infrared_worker.h>
#include "scene/infrared_app_scene.h"
#include "scene/infrared_app_scene.h"
#include "infrared_app_view_manager.h"
#include "infrared_app_remote_manager.h"
#include "infrared_app_view_manager.h"
/** Main Infrared application class */
class InfraredApp {
public:
/** Enum to save scene state: edit element */
enum class EditElement : uint8_t {
Button,
Remote,
};
/** Enum to save scene state: edit action */
enum class EditAction : uint8_t {
Rename,
Delete,
};
/** List of scenes for Infrared application */
enum class Scene : uint8_t {
Exit,
Start,
Universal,
UniversalTV,
UniversalAudio,
UniversalAirConditioner,
Learn,
LearnSuccess,
LearnEnterName,
LearnDone,
AskBack,
Remote,
RemoteList,
Edit,
EditKeySelect,
EditRename,
EditDelete,
EditRenameDone,
EditDeleteDone,
};
/** Start application
*
* @param args - application arguments.
* Allowed argument is path to remote file.
* @retval 0 on success, error code otherwise
*/
int32_t run(void* args);
/** Switch to next scene. Put current scene number on stack.
* Doesn't save scene state.
*
* @param index - next scene index
*/
void switch_to_next_scene(Scene index);
/** Switch to next scene, but don't put current scene on
* stack. Thus calling switch_to_previous_scene() doesn't return
* to current scene.
*
* @param index - next scene index
*/
void switch_to_next_scene_without_saving(Scene index);
/** Switch to previous scene. Pop scenes from stack and switch to last one.
*
* @param count - how many scenes should be popped
* @retval false on failed, true on success
*/
bool switch_to_previous_scene(uint8_t count = 1);
/** Get previous scene in scene stack
*
* @retval previous scene
*/
Scene get_previous_scene();
/** Get view manager instance
*
* @retval view manager instance
*/
InfraredAppViewManager* get_view_manager();
/** Set one of text stores
*
* @param index - index of text store
* @param text - text to set
*/
void set_text_store(uint8_t index, const char* text...);
/** Get value in text store
*
* @param index - index of text store
* @retval value in text_store
*/
char* get_text_store(uint8_t index);
/** Get text store size
*
* @retval size of text store
*/
uint8_t get_text_store_size();
/** Get remote manager instance
*
* @retval remote manager instance
*/
InfraredAppRemoteManager* get_remote_manager();
/** Get infrared worker instance
*
* @retval infrared worker instance
*/
InfraredWorker* get_infrared_worker();
/** Get signal, previously got on Learn scene
*
* @retval received signal
*/
const InfraredAppSignal& get_received_signal() const;
/** Set received signal
*
* @param signal - signal
*/
void set_received_signal(const InfraredAppSignal& signal);
/** Switch to previous scene in one of provided in list.
* Pop scene stack, and find first scene from list.
*
* @param scenes_list - list of scenes
*/
void search_and_switch_to_previous_scene(const std::initializer_list<Scene>& scenes_list);
/** Set edit element value. It is used on edit scene to determine
* what should be deleted - remote or button.
*
* @param value - value to set
*/
void set_edit_element(EditElement value);
/** Get edit element
*
* @retval edit element value
*/
EditElement get_edit_element(void);
/** Set edit action value. It is used on edit scene to determine
* what action to perform - deletion or renaming.
*
* @param value - value to set
*/
void set_edit_action(EditAction value);
/** Get edit action
*
* @retval edit action value
*/
EditAction get_edit_action(void);
/** Get state of learning new signal.
* Adding new remote with 1 button from start scene and
* learning 1 additional button to remote have very similar
* flow, so they are joined. Difference in flow is handled
* by this boolean flag.
*
* @retval false if flow is in learning new remote, true if
* adding signal to created remote
*
*/
bool get_learn_new_remote();
/** Set state of learning new signal.
* Adding new remote with 1 button from start scene and
* learning 1 additional button to remote have very similar
* flow, so they are joined. Difference in flow is handled
* by this boolean flag.
*
* @param value - false if flow is in learning new remote, true if
* adding signal to created remote
*/
void set_learn_new_remote(bool value);
/** Button is not assigned value
*/
enum : int {
ButtonNA = -1,
};
/** Get current button index
*
* @retval current button index
*/
int get_current_button();
/** Set current button index
*
* @param current button index
*/
void set_current_button(int value);
/** Play success notification */
void notify_success();
/** Play red blink notification */
void notify_blink_read();
/** Light green */
void notify_green_on();
/** Disable green light */
void notify_green_off();
/** Blink on send */
void notify_blink_send();
/** Get Dialogs instance */
DialogsApp* get_dialogs();
/** Text input callback
*
* @param context - context to pass to callback
*/
static void text_input_callback(void* context);
/** Popup callback
*
* @param context - context to pass to callback
*/
static void popup_callback(void* context);
/** Signal sent callback
*
* @param context - context to pass to callback
*/
static void signal_sent_callback(void* context);
/** Main class constructor, initializes all critical objects */
InfraredApp();
/** Main class destructor, deinitializes all critical objects */
~InfraredApp();
string_t file_path;
/** Path to Infrared directory */
static constexpr const char* infrared_directory = "/any/infrared";
/** Infrared files extension (remote files and universal databases) */
static constexpr const char* infrared_extension = ".ir";
/** Max Raw timings in signal */
static constexpr const uint32_t max_raw_timings_in_signal = 512;
/** Max line length in Infrared file */
static constexpr const uint32_t max_line_length =
(9 + 1) * InfraredApp::max_raw_timings_in_signal + 100;
private:
/** Text store size */
static constexpr const uint8_t text_store_size = 128;
/** Amount of text stores */
static constexpr const uint8_t text_store_max = 2;
/** Store text here, for some views, because they doesn't
* hold ownership of text */
char text_store[text_store_max][text_store_size + 1];
/**
* Flag to control adding new signal flow.
* Adding new remote with 1 button from start scene and
* learning 1 additional button to remote have very similar
* flow, so they are joined. Difference in flow is handled
* by this boolean flag.
*/
bool learn_new_remote;
/** Value to control edit scene */
EditElement element;
/** Value to control edit scene */
EditAction action;
/** Selected button index */
uint32_t current_button;
/** Notification instance */
NotificationApp* notification;
/** Dialogs instance */
DialogsApp* dialogs;
/** View manager instance */
InfraredAppViewManager view_manager;
/** Remote manager instance */
InfraredAppRemoteManager remote_manager;
/** Infrared worker instance */
InfraredWorker* infrared_worker;
/** Signal received on Learn scene */
InfraredAppSignal received_signal;
/** Stack of previous scenes */
std::forward_list<Scene> previous_scenes_list;
/** Now acting scene */
Scene current_scene = Scene::Start;
/** Map of index/scene objects */
std::map<Scene, InfraredAppScene*> scenes = {
{Scene::Start, new InfraredAppSceneStart()},
{Scene::Universal, new InfraredAppSceneUniversal()},
{Scene::UniversalTV, new InfraredAppSceneUniversalTV()},
{Scene::Learn, new InfraredAppSceneLearn()},
{Scene::LearnSuccess, new InfraredAppSceneLearnSuccess()},
{Scene::LearnEnterName, new InfraredAppSceneLearnEnterName()},
{Scene::LearnDone, new InfraredAppSceneLearnDone()},
{Scene::AskBack, new InfraredAppSceneAskBack()},
{Scene::Remote, new InfraredAppSceneRemote()},
{Scene::RemoteList, new InfraredAppSceneRemoteList()},
{Scene::Edit, new InfraredAppSceneEdit()},
{Scene::EditKeySelect, new InfraredAppSceneEditKeySelect()},
{Scene::EditRename, new InfraredAppSceneEditRename()},
{Scene::EditDelete, new InfraredAppSceneEditDelete()},
{Scene::EditRenameDone, new InfraredAppSceneEditRenameDone()},
{Scene::EditDeleteDone, new InfraredAppSceneEditDeleteDone()},
};
};

View file

@ -1,93 +0,0 @@
#include "helpers/infrared_parser.h"
#include "infrared_app_brute_force.h"
#include "infrared_app_signal.h"
#include <memory>
#include <m-string.h>
#include <furi.h>
void InfraredAppBruteForce::add_record(int index, const char* name) {
records[name].index = index;
records[name].amount = 0;
}
bool InfraredAppBruteForce::calculate_messages() {
bool result = false;
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
FlipperFormat* ff = flipper_format_file_alloc(storage);
result = flipper_format_file_open_existing(ff, universal_db_filename);
if(result) {
InfraredAppSignal signal;
string_t signal_name;
string_init(signal_name);
while(flipper_format_read_string(ff, "name", signal_name)) {
auto element = records.find(string_get_cstr(signal_name));
if(element != records.cend()) {
++element->second.amount;
}
}
string_clear(signal_name);
}
flipper_format_free(ff);
furi_record_close("storage");
return result;
}
void InfraredAppBruteForce::stop_bruteforce() {
furi_assert((current_record.size()));
if(current_record.size()) {
furi_assert(ff);
current_record.clear();
flipper_format_free(ff);
furi_record_close("storage");
}
}
bool InfraredAppBruteForce::send_next_bruteforce(void) {
furi_assert(current_record.size());
furi_assert(ff);
InfraredAppSignal signal;
std::string signal_name;
bool result = false;
do {
result = infrared_parser_read_signal(ff, signal, signal_name);
} while(result && current_record.compare(signal_name));
if(result) {
signal.transmit();
}
return result;
}
bool InfraredAppBruteForce::start_bruteforce(int index, int& record_amount) {
bool result = false;
record_amount = 0;
for(const auto& it : records) {
if(it.second.index == index) {
record_amount = it.second.amount;
if(record_amount) {
current_record = it.first;
}
break;
}
}
if(record_amount) {
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
ff = flipper_format_file_alloc(storage);
result = flipper_format_file_open_existing(ff, universal_db_filename);
if(!result) {
flipper_format_free(ff);
furi_record_close("storage");
}
}
return result;
}

View file

@ -1,67 +0,0 @@
/**
* @file infrared_app_brute_force.h
* Infrared: Brute Force class description
*/
#pragma once
#include <unordered_map>
#include <memory>
#include <flipper_format/flipper_format.h>
/** Class handles brute force mechanic */
class InfraredAppBruteForce {
/** Universal database filename */
const char* universal_db_filename;
/** Current record name (POWER, MUTE, VOL+, etc).
* This is the name of signal to brute force. */
std::string current_record;
/** Flipper File Format instance */
FlipperFormat* ff;
/** Data about every record - index in button panel view
* and amount of signals, which is need for correct
* progress bar displaying. */
typedef struct {
/** Index of record in button panel view model */
int index;
/** Amount of signals of that type (POWER, MUTE, etc) */
int amount;
} Record;
/** Container to hold Record info.
* 'key' is record name, because we have to search by both, index and name,
* but index search has place once per button press, and should not be
* noticed, but name search should occur during entering universal menu,
* and will go through container for every record in file, that's why
* more critical to have faster search by record name.
*/
std::unordered_map<std::string, Record> records;
public:
/** Calculate messages. Walk through the file ('universal_db_name')
* and calculate amount of records of certain type. */
bool calculate_messages();
/** Start brute force */
bool start_bruteforce(int index, int& record_amount);
/** Stop brute force */
void stop_bruteforce();
/** Send next signal during brute force */
bool send_next_bruteforce();
/** Add record to container of records */
void add_record(int index, const char* name);
/** Initialize class, set db file */
InfraredAppBruteForce(const char* filename)
: universal_db_filename(filename) {
}
/** Deinitialize class */
~InfraredAppBruteForce() {
}
};

View file

@ -1,48 +0,0 @@
/**
* @file infrared_app_event.h
* Infrared: Scene events description
*/
#pragma once
#include <infrared.h>
#include <gui/modules/dialog_ex.h>
/** Infrared events class */
class InfraredAppEvent {
public:
/** Type of event enum */
enum class Type : uint8_t {
/** Tick event come after no other events came in 100 ms */
Tick,
/** Exit application event */
Exit,
/** Back event */
Back,
/** Menu selected event type. Provided with payload value. */
MenuSelected,
/** Button press event. Need for continuous signal sending. */
MenuSelectedPress,
/** Button release event. Need for continuous signal sending. */
MenuSelectedRelease,
/** Events from DialogEx view module */
DialogExSelected,
/** Infrared signal received event */
InfraredMessageReceived,
/** Text edit done event */
TextEditDone,
/** Popup timer finished event */
PopupTimer,
/** Button panel pressed event */
ButtonPanelPressed,
};
union {
int32_t dummy;
/** Menu selected event type payload. Selected index. */
int32_t menu_index;
/** DialogEx view module event type payload */
DialogExResult dialog_ex_result;
} payload;
/** Type of event */
Type type;
};

View file

@ -1,266 +0,0 @@
#include "m-string.h"
#include "storage/filesystem_api_defines.h"
#include <flipper_format/flipper_format.h>
#include "infrared_app_remote_manager.h"
#include "infrared/helpers/infrared_parser.h"
#include "infrared/infrared_app_signal.h"
#include <utility>
#include <infrared.h>
#include <cstdio>
#include <furi.h>
#include <gui/modules/button_menu.h>
#include <storage/storage.h>
#include "infrared_app.h"
#include <toolbox/path.h>
static const char* default_remote_name = "remote";
void InfraredAppRemoteManager::find_vacant_remote_name(string_t name, string_t path) {
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
string_t base_path;
string_init_set(base_path, path);
if(string_end_with_str_p(base_path, InfraredApp::infrared_extension)) {
size_t filename_start = string_search_rchar(base_path, '/');
string_left(base_path, filename_start);
}
string_printf(
base_path,
"%s/%s%s",
string_get_cstr(path),
string_get_cstr(name),
InfraredApp::infrared_extension);
FS_Error error = storage_common_stat(storage, string_get_cstr(base_path), NULL);
if(error == FSE_OK) {
/* if suggested name is occupied, try another one (name2, name3, etc) */
size_t dot = string_search_rchar(base_path, '.');
string_left(base_path, dot);
string_t path_temp;
string_init(path_temp);
uint32_t i = 1;
do {
string_printf(
path_temp,
"%s%u%s",
string_get_cstr(base_path),
++i,
InfraredApp::infrared_extension);
error = storage_common_stat(storage, string_get_cstr(path_temp), NULL);
} while(error == FSE_OK);
string_clear(path_temp);
if(error == FSE_NOT_EXIST) {
string_cat_printf(name, "%u", i);
}
}
string_clear(base_path);
furi_record_close("storage");
}
bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) {
remote->buttons.emplace_back(button_name, signal);
return store();
}
bool InfraredAppRemoteManager::add_remote_with_button(
const char* button_name,
const InfraredAppSignal& signal) {
furi_check(button_name != nullptr);
string_t new_name;
string_init_set_str(new_name, default_remote_name);
string_t new_path;
string_init_set_str(new_path, InfraredApp::infrared_directory);
find_vacant_remote_name(new_name, new_path);
string_cat_printf(
new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension);
remote = std::make_unique<InfraredAppRemote>(new_path);
remote->name = std::string(string_get_cstr(new_name));
string_clear(new_path);
string_clear(new_name);
return add_button(button_name, signal);
}
std::vector<std::string> InfraredAppRemoteManager::get_button_list(void) const {
std::vector<std::string> name_vector;
name_vector.reserve(remote->buttons.size());
for(const auto& it : remote->buttons) {
name_vector.emplace_back(it.name);
}
// copy elision
return name_vector;
}
const InfraredAppSignal& InfraredAppRemoteManager::get_button_data(size_t index) const {
furi_check(remote.get() != nullptr);
auto& buttons = remote->buttons;
furi_check(index < buttons.size());
return buttons.at(index).signal;
}
bool InfraredAppRemoteManager::delete_remote() {
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
FS_Error error = storage_common_remove(storage, string_get_cstr(remote->path));
reset_remote();
furi_record_close("storage");
return (error == FSE_OK || error == FSE_NOT_EXIST);
}
void InfraredAppRemoteManager::reset_remote() {
remote.reset();
}
bool InfraredAppRemoteManager::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 InfraredAppRemoteManager::get_button_name(uint32_t index) {
furi_check(remote.get() != nullptr);
auto& buttons = remote->buttons;
furi_check(index < buttons.size());
return buttons[index].name.c_str();
}
std::string InfraredAppRemoteManager::get_remote_name() {
return remote.get() ? remote->name : std::string();
}
bool InfraredAppRemoteManager::rename_remote(const char* str) {
furi_check(str != nullptr);
furi_check(remote.get() != nullptr);
furi_check(!string_empty_p(remote->path));
if(!remote->name.compare(str)) {
return true;
}
string_t new_name;
string_init_set_str(new_name, str);
find_vacant_remote_name(new_name, remote->path);
string_t new_path;
string_init_set(new_path, remote->path);
if(string_end_with_str_p(new_path, InfraredApp::infrared_extension)) {
size_t filename_start = string_search_rchar(new_path, '/');
string_left(new_path, filename_start);
}
string_cat_printf(
new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension);
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
FS_Error error =
storage_common_rename(storage, string_get_cstr(remote->path), string_get_cstr(new_path));
remote->name = std::string(string_get_cstr(new_name));
string_clear(new_name);
string_clear(new_path);
furi_record_close("storage");
return (error == FSE_OK || error == FSE_EXIST);
}
bool InfraredAppRemoteManager::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 InfraredAppRemoteManager::get_number_of_buttons() {
furi_check(remote.get() != nullptr);
return remote->buttons.size();
}
bool InfraredAppRemoteManager::store(void) {
bool result = false;
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
if(!storage_simply_mkdir(storage, InfraredApp::infrared_directory)) return false;
FlipperFormat* ff = flipper_format_file_alloc(storage);
FURI_LOG_I("RemoteManager", "store file: \'%s\'", string_get_cstr(remote->path));
result = flipper_format_file_open_always(ff, string_get_cstr(remote->path));
if(result) {
result = flipper_format_write_header_cstr(ff, "IR signals file", 1);
}
if(result) {
for(const auto& button : remote->buttons) {
result = infrared_parser_save_signal(ff, button.signal, button.name.c_str());
if(!result) {
break;
}
}
}
flipper_format_free(ff);
furi_record_close("storage");
return result;
}
bool InfraredAppRemoteManager::load(string_t path) {
bool result = false;
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
FlipperFormat* ff = flipper_format_file_alloc(storage);
FURI_LOG_I("RemoteManager", "load file: \'%s\'", string_get_cstr(path));
result = flipper_format_file_open_existing(ff, string_get_cstr(path));
if(result) {
string_t header;
string_init(header);
uint32_t version;
result = flipper_format_read_header(ff, header, &version);
if(result) {
result = !string_cmp_str(header, "IR signals file") && (version == 1);
}
string_clear(header);
}
if(result) {
string_t new_name;
string_init(new_name);
remote = std::make_unique<InfraredAppRemote>(path);
path_extract_filename(path, new_name, true);
remote->name = std::string(string_get_cstr(new_name));
string_clear(new_name);
InfraredAppSignal signal;
std::string signal_name;
while(infrared_parser_read_signal(ff, signal, signal_name)) {
remote->buttons.emplace_back(signal_name.c_str(), std::move(signal));
}
}
flipper_format_free(ff);
furi_record_close("storage");
return result;
}

View file

@ -1,189 +0,0 @@
/**
* @file infrared_app_remote_manager.h
* Infrared: Remote manager class.
* It holds remote, can load/save/rename remote,
* add/remove/rename buttons.
*/
#pragma once
#include "infrared_app_signal.h"
#include "m-string.h"
#include <infrared_worker.h>
#include <infrared.h>
#include <cstdint>
#include <string>
#include <memory>
#include <vector>
/** Class to handle remote button */
class InfraredAppRemoteButton {
/** Allow field access */
friend class InfraredAppRemoteManager;
/** Name of signal */
std::string name;
/** Signal data */
InfraredAppSignal signal;
public:
/** Initialize remote button
*
* @param name - button name
* @param signal - signal to copy for remote button
*/
InfraredAppRemoteButton(const char* name, const InfraredAppSignal& signal)
: name(name)
, signal(signal) {
}
/** Initialize remote button
*
* @param name - button name
* @param signal - signal to move for remote button
*/
InfraredAppRemoteButton(const char* name, InfraredAppSignal&& signal)
: name(name)
, signal(std::move(signal)) {
}
/** Deinitialize remote button */
~InfraredAppRemoteButton() {
}
};
/** Class to handle remote */
class InfraredAppRemote {
/** Allow field access */
friend class InfraredAppRemoteManager;
/** Button container */
std::vector<InfraredAppRemoteButton> buttons;
/** Name of remote */
std::string name;
/** Path to remote file */
string_t path;
public:
/** Initialize new remote
*
* @param path - remote file path
*/
InfraredAppRemote(string_t file_path) {
string_init_set(path, file_path);
}
~InfraredAppRemote() {
string_clear(path);
}
};
/** Class to handle remote manager */
class InfraredAppRemoteManager {
/** Remote instance. There can be 1 remote loaded at a time. */
std::unique_ptr<InfraredAppRemote> remote;
public:
/** Restriction to button name length. Buttons larger are ignored. */
static constexpr const uint32_t max_button_name_length = 22;
/** Restriction to remote name length. Remotes larger are ignored. */
static constexpr const uint32_t max_remote_name_length = 22;
/** Construct button from signal, and create remote
*
* @param button_name - name of button to create
* @param signal - signal to create button from
* @retval true for success, false otherwise
* */
bool add_remote_with_button(const char* button_name, const InfraredAppSignal& signal);
/** Add button to current remote
*
* @param button_name - name of button to create
* @param signal - signal to create button from
* @retval true for success, false otherwise
* */
bool add_button(const char* button_name, const InfraredAppSignal& signal);
/** Rename button in current remote
*
* @param index - index of button to rename
* @param str - new button name
*/
bool rename_button(uint32_t index, const char* str);
/** Rename current remote
*
* @param str - new remote name
*/
bool rename_remote(const char* str);
/** Find vacant remote name. If suggested name is occupied,
* incremented digit(2,3,4,etc) added to name and check repeated.
*
* @param name - suggested remote name
* @param path - remote file path
*/
void find_vacant_remote_name(string_t name, string_t path);
/** Get button list
*
* @retval container of button names
*/
std::vector<std::string> get_button_list() const;
/** Get button name by index
*
* @param index - index of button to get name from
* @retval button name
*/
std::string get_button_name(uint32_t index);
/** Get remote name
*
* @retval remote name
*/
std::string get_remote_name();
/** Get number of buttons
*
* @retval number of buttons
*/
size_t get_number_of_buttons();
/** Get button's signal
*
* @param index - index of interested button
* @retval signal
*/
const InfraredAppSignal& get_button_data(size_t index) const;
/** Delete button
*
* @param index - index of interested button
* @retval true if success, false otherwise
*/
bool delete_button(uint32_t index);
/** Delete remote
*
* @retval true if success, false otherwise
*/
bool delete_remote();
/** Clean all loaded info in current remote */
void reset_remote();
/** Store current remote data on disk
*
* @retval true if success, false otherwise
*/
bool store();
/** Load data from disk into current remote
*
* @param path - path to remote file
* @retval true if success, false otherwise
*/
bool load(string_t path);
};

View file

@ -1,116 +0,0 @@
#include "infrared_app_signal.h"
#include <infrared_transmit.h>
void InfraredAppSignal::copy_raw_signal(
const uint32_t* timings,
size_t size,
uint32_t frequency,
float duty_cycle) {
furi_assert(size);
furi_assert(timings);
payload.raw.frequency = frequency;
payload.raw.duty_cycle = duty_cycle;
payload.raw.timings_cnt = size;
if(size) {
payload.raw.timings = new uint32_t[size];
memcpy(payload.raw.timings, timings, size * sizeof(uint32_t));
}
}
void InfraredAppSignal::clear_timings() {
if(raw_signal) {
delete[] payload.raw.timings;
payload.raw.timings_cnt = 0;
payload.raw.timings = nullptr;
}
}
InfraredAppSignal::InfraredAppSignal(
const uint32_t* timings,
size_t timings_cnt,
uint32_t frequency,
float duty_cycle) {
raw_signal = true;
copy_raw_signal(timings, timings_cnt, frequency, duty_cycle);
}
InfraredAppSignal::InfraredAppSignal(const InfraredMessage* infrared_message) {
raw_signal = false;
payload.message = *infrared_message;
}
InfraredAppSignal& InfraredAppSignal::operator=(const InfraredAppSignal& other) {
clear_timings();
raw_signal = other.raw_signal;
if(!raw_signal) {
payload.message = other.payload.message;
} else {
copy_raw_signal(
other.payload.raw.timings,
other.payload.raw.timings_cnt,
other.payload.raw.frequency,
other.payload.raw.duty_cycle);
}
return *this;
}
InfraredAppSignal::InfraredAppSignal(const InfraredAppSignal& other) {
raw_signal = other.raw_signal;
if(!raw_signal) {
payload.message = other.payload.message;
} else {
copy_raw_signal(
other.payload.raw.timings,
other.payload.raw.timings_cnt,
other.payload.raw.frequency,
other.payload.raw.duty_cycle);
}
}
InfraredAppSignal::InfraredAppSignal(InfraredAppSignal&& other) {
raw_signal = other.raw_signal;
if(!raw_signal) {
payload.message = other.payload.message;
} else {
furi_assert(other.payload.raw.timings_cnt > 0);
payload.raw.timings = other.payload.raw.timings;
payload.raw.timings_cnt = other.payload.raw.timings_cnt;
payload.raw.frequency = other.payload.raw.frequency;
payload.raw.duty_cycle = other.payload.raw.duty_cycle;
other.payload.raw.timings = nullptr;
other.payload.raw.timings_cnt = 0;
other.raw_signal = false;
}
}
void InfraredAppSignal::set_message(const InfraredMessage* infrared_message) {
clear_timings();
raw_signal = false;
payload.message = *infrared_message;
}
void InfraredAppSignal::set_raw_signal(
uint32_t* timings,
size_t timings_cnt,
uint32_t frequency,
float duty_cycle) {
clear_timings();
raw_signal = true;
copy_raw_signal(timings, timings_cnt, frequency, duty_cycle);
}
void InfraredAppSignal::transmit() const {
if(!raw_signal) {
infrared_send(&payload.message, 1);
} else {
infrared_send_raw_ext(
payload.raw.timings,
payload.raw.timings_cnt,
true,
payload.raw.frequency,
payload.raw.duty_cycle);
}
}

View file

@ -1,134 +0,0 @@
/**
* @file infrared_app_signal.h
* Infrared: Signal class
*/
#pragma once
#include <infrared_worker.h>
#include <stdint.h>
#include <string>
#include <infrared.h>
/** Infrared application signal class */
class InfraredAppSignal {
public:
/** Raw signal structure */
typedef struct {
/** Timings amount */
size_t timings_cnt;
/** Samples of raw signal in ms */
uint32_t* timings;
/** PWM Frequency of raw signal */
uint32_t frequency;
/** PWM Duty cycle of raw signal */
float duty_cycle;
} RawSignal;
private:
/** if true - signal is raw, if false - signal is parsed */
bool raw_signal;
/** signal data, either raw or parsed */
union {
/** signal data for parsed signal */
InfraredMessage message;
/** raw signal data */
RawSignal raw;
} payload;
/** Copy raw signal into object
*
* @param timings - timings (samples) of raw signal
* @param size - number of timings
* @frequency - PWM frequency of raw signal
* @duty_cycle - PWM duty cycle
*/
void
copy_raw_signal(const uint32_t* timings, size_t size, uint32_t frequency, float duty_cycle);
/** Clear and free timings data */
void clear_timings();
public:
/** Construct Infrared signal class */
InfraredAppSignal() {
raw_signal = false;
payload.message.protocol = InfraredProtocolUnknown;
}
/** Destruct signal class and free all allocated data */
~InfraredAppSignal() {
clear_timings();
}
/** Construct object with raw signal
*
* @param timings - timings (samples) of raw signal
* @param size - number of timings
* @frequency - PWM frequency of raw signal
* @duty_cycle - PWM duty cycle
*/
InfraredAppSignal(
const uint32_t* timings,
size_t timings_cnt,
uint32_t frequency,
float duty_cycle);
/** Construct object with parsed signal
*
* @param infrared_message - parsed_signal to construct from
*/
InfraredAppSignal(const InfraredMessage* infrared_message);
/** Copy constructor */
InfraredAppSignal(const InfraredAppSignal& other);
/** Move constructor */
InfraredAppSignal(InfraredAppSignal&& other);
/** Assignment operator */
InfraredAppSignal& operator=(const InfraredAppSignal& signal);
/** Set object to parsed signal
*
* @param infrared_message - parsed_signal to construct from
*/
void set_message(const InfraredMessage* infrared_message);
/** Set object to raw signal
*
* @param timings - timings (samples) of raw signal
* @param size - number of timings
* @frequency - PWM frequency of raw signal
* @duty_cycle - PWM duty cycle
*/
void
set_raw_signal(uint32_t* timings, size_t timings_cnt, uint32_t frequency, float duty_cycle);
/** Transmit held signal (???) */
void transmit() const;
/** Show is held signal raw
*
* @retval true if signal is raw, false if signal is parsed
*/
bool is_raw(void) const {
return raw_signal;
}
/** Get parsed signal.
* User must check is_raw() signal before calling this function.
*
* @retval parsed signal pointer
*/
const InfraredMessage& get_message(void) const {
furi_assert(!raw_signal);
return payload.message;
}
/** Get raw signal.
* User must check is_raw() signal before calling this function.
*
* @retval raw signal
*/
const RawSignal& get_raw_signal(void) const {
furi_assert(raw_signal);
return payload.raw;
}
};

View file

@ -1,163 +0,0 @@
#include <gui/modules/button_menu.h>
#include <gui/view_stack.h>
#include <gui/modules/loading.h>
#include <gui/modules/button_panel.h>
#include <gui/modules/dialog_ex.h>
#include <furi.h>
#include <callback-connector.h>
#include "infrared/infrared_app_view_manager.h"
#include "infrared/view/infrared_progress_view.h"
#include "infrared_app.h"
#include "infrared/infrared_app_event.h"
InfraredAppViewManager::InfraredAppViewManager() {
event_queue = osMessageQueueNew(10, sizeof(InfraredAppEvent), NULL);
view_dispatcher = view_dispatcher_alloc();
auto callback = cbc::obtain_connector(this, &InfraredAppViewManager::previous_view_callback);
gui = static_cast<Gui*>(furi_record_open("gui"));
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
button_menu = button_menu_alloc();
submenu = submenu_alloc();
popup = popup_alloc();
dialog_ex = dialog_ex_alloc();
text_input = text_input_alloc();
button_panel = button_panel_alloc();
progress_view = infrared_progress_view_alloc();
loading_view = loading_alloc();
universal_view_stack = view_stack_alloc();
view_stack_add_view(universal_view_stack, button_panel_get_view(button_panel));
view_set_orientation(view_stack_get_view(universal_view_stack), ViewOrientationVertical);
add_view(ViewId::UniversalRemote, view_stack_get_view(universal_view_stack));
add_view(ViewId::ButtonMenu, button_menu_get_view(button_menu));
add_view(ViewId::Submenu, submenu_get_view(submenu));
add_view(ViewId::Popup, popup_get_view(popup));
add_view(ViewId::DialogEx, dialog_ex_get_view(dialog_ex));
add_view(ViewId::TextInput, text_input_get_view(text_input));
view_set_previous_callback(view_stack_get_view(universal_view_stack), callback);
view_set_previous_callback(button_menu_get_view(button_menu), callback);
view_set_previous_callback(submenu_get_view(submenu), callback);
view_set_previous_callback(popup_get_view(popup), callback);
view_set_previous_callback(dialog_ex_get_view(dialog_ex), callback);
view_set_previous_callback(text_input_get_view(text_input), callback);
}
InfraredAppViewManager::~InfraredAppViewManager() {
view_dispatcher_remove_view(
view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::UniversalRemote));
view_dispatcher_remove_view(
view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::ButtonMenu));
view_dispatcher_remove_view(
view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::TextInput));
view_dispatcher_remove_view(
view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::DialogEx));
view_dispatcher_remove_view(
view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::Submenu));
view_dispatcher_remove_view(
view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::Popup));
view_stack_remove_view(universal_view_stack, button_panel_get_view(button_panel));
view_stack_free(universal_view_stack);
button_panel_free(button_panel);
submenu_free(submenu);
popup_free(popup);
button_menu_free(button_menu);
dialog_ex_free(dialog_ex);
text_input_free(text_input);
infrared_progress_view_free(progress_view);
loading_free(loading_view);
view_dispatcher_free(view_dispatcher);
furi_record_close("gui");
osMessageQueueDelete(event_queue);
}
void InfraredAppViewManager::switch_to(ViewId type) {
view_dispatcher_switch_to_view(view_dispatcher, static_cast<uint32_t>(type));
}
TextInput* InfraredAppViewManager::get_text_input() {
return text_input;
}
DialogEx* InfraredAppViewManager::get_dialog_ex() {
return dialog_ex;
}
Submenu* InfraredAppViewManager::get_submenu() {
return submenu;
}
Popup* InfraredAppViewManager::get_popup() {
return popup;
}
ButtonMenu* InfraredAppViewManager::get_button_menu() {
return button_menu;
}
ButtonPanel* InfraredAppViewManager::get_button_panel() {
return button_panel;
}
InfraredProgressView* InfraredAppViewManager::get_progress() {
return progress_view;
}
Loading* InfraredAppViewManager::get_loading() {
return loading_view;
}
ViewStack* InfraredAppViewManager::get_universal_view_stack() {
return universal_view_stack;
}
osMessageQueueId_t InfraredAppViewManager::get_event_queue() {
return event_queue;
}
void InfraredAppViewManager::clear_events() {
InfraredAppEvent event;
while(osMessageQueueGet(event_queue, &event, NULL, 0) == osOK)
;
}
void InfraredAppViewManager::receive_event(InfraredAppEvent* event) {
if(osMessageQueueGet(event_queue, event, NULL, 100) != osOK) {
event->type = InfraredAppEvent::Type::Tick;
}
}
void InfraredAppViewManager::send_event(InfraredAppEvent* event) {
uint32_t timeout = 0;
/* Rapid button hammering on signal send scenes causes queue overflow - ignore it,
* but try to keep button release event - it switches off INFRARED DMA sending. */
if(event->type == InfraredAppEvent::Type::MenuSelectedRelease) {
timeout = 200;
}
if((event->type == InfraredAppEvent::Type::DialogExSelected) &&
(event->payload.dialog_ex_result == DialogExReleaseCenter)) {
timeout = 200;
}
osMessageQueuePut(event_queue, event, 0, timeout);
}
uint32_t InfraredAppViewManager::previous_view_callback(void*) {
if(event_queue != NULL) {
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::Back;
send_event(&event);
}
return VIEW_IGNORE;
}
void InfraredAppViewManager::add_view(ViewId view_type, View* view) {
view_dispatcher_add_view(view_dispatcher, static_cast<uint32_t>(view_type), view);
}

View file

@ -1,164 +0,0 @@
/**
* @file infrared_app_view_manager.h
* Infrared: Scene events description
*/
#pragma once
#include <gui/modules/button_menu.h>
#include <gui/modules/text_input.h>
#include <gui/view_stack.h>
#include <gui/modules/button_panel.h>
#include <furi.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/dialog_ex.h>
#include <gui/modules/submenu.h>
#include <gui/modules/popup.h>
#include <gui/modules/loading.h>
#include "infrared_app_event.h"
#include "view/infrared_progress_view.h"
/** Infrared View manager class */
class InfraredAppViewManager {
public:
/** Infrared View Id enum, it is used
* to identify added views */
enum class ViewId : uint8_t {
DialogEx,
TextInput,
Submenu,
ButtonMenu,
UniversalRemote,
Popup,
};
/** Class constructor */
InfraredAppViewManager();
/** Class destructor */
~InfraredAppViewManager();
/** Switch to another view
*
* @param id - view id to switch to
*/
void switch_to(ViewId id);
/** Receive event from queue
*
* @param event - received event
*/
void receive_event(InfraredAppEvent* event);
/** Send event to queue
*
* @param event - event to send
*/
void send_event(InfraredAppEvent* event);
/** Clear events that already in queue
*
* @param event - event to send
*/
void clear_events();
/** Get dialog_ex view module
*
* @retval dialog_ex view module
*/
DialogEx* get_dialog_ex();
/** Get submenu view module
*
* @retval submenu view module
*/
Submenu* get_submenu();
/** Get popup view module
*
* @retval popup view module
*/
Popup* get_popup();
/** Get text_input view module
*
* @retval text_input view module
*/
TextInput* get_text_input();
/** Get button_menu view module
*
* @retval button_menu view module
*/
ButtonMenu* get_button_menu();
/** Get button_panel view module
*
* @retval button_panel view module
*/
ButtonPanel* get_button_panel();
/** Get view_stack view module used in universal remote
*
* @retval view_stack view module
*/
ViewStack* get_universal_view_stack();
/** Get progress view module
*
* @retval progress view module
*/
InfraredProgressView* get_progress();
/** Get loading view module
*
* @retval loading view module
*/
Loading* get_loading();
/** Get event queue
*
* @retval event queue
*/
osMessageQueueId_t get_event_queue();
/** Callback to handle back button
*
* @param context - context to pass to callback
* @retval always returns VIEW_IGNORE
*/
uint32_t previous_view_callback(void* context);
private:
/** View Dispatcher instance.
* It handles view switching */
ViewDispatcher* view_dispatcher;
/** Gui instance */
Gui* gui;
/** Text input view module instance */
TextInput* text_input;
/** DialogEx view module instance */
DialogEx* dialog_ex;
/** Submenu view module instance */
Submenu* submenu;
/** Popup view module instance */
Popup* popup;
/** ButtonMenu view module instance */
ButtonMenu* button_menu;
/** ButtonPanel view module instance */
ButtonPanel* button_panel;
/** ViewStack view module instance */
ViewStack* universal_view_stack;
/** ProgressView view module instance */
InfraredProgressView* progress_view;
/** Loading view module instance */
Loading* loading_view;
/** Queue to handle events, which are processed in scenes */
osMessageQueueId_t event_queue;
/** Add View to pull of views
*
* @param view_id - id to identify view
* @param view - view to add
*/
void add_view(ViewId view_id, View* view);
};

View file

@ -0,0 +1,153 @@
#include "infrared_brute_force.h"
#include <stdlib.h>
#include <m-dict.h>
#include <m-string.h>
#include <flipper_format/flipper_format.h>
#include "infrared_signal.h"
typedef struct {
uint32_t index;
uint32_t count;
} InfraredBruteForceRecord;
DICT_DEF2(
InfraredBruteForceRecordDict,
string_t,
STRING_OPLIST,
InfraredBruteForceRecord,
M_POD_OPLIST);
struct InfraredBruteForce {
FlipperFormat* ff;
const char* db_filename;
string_t current_record_name;
InfraredBruteForceRecordDict_t records;
};
InfraredBruteForce* infrared_brute_force_alloc() {
InfraredBruteForce* brute_force = malloc(sizeof(InfraredBruteForce));
brute_force->ff = NULL;
brute_force->db_filename = NULL;
string_init(brute_force->current_record_name);
InfraredBruteForceRecordDict_init(brute_force->records);
return brute_force;
}
void infrared_brute_force_free(InfraredBruteForce* brute_force) {
furi_assert(!brute_force->ff);
InfraredBruteForceRecordDict_clear(brute_force->records);
string_clear(brute_force->current_record_name);
free(brute_force);
}
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) {
brute_force->db_filename = db_filename;
}
bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) {
furi_assert(brute_force->db_filename);
bool success = false;
Storage* storage = furi_record_open("storage");
FlipperFormat* ff = flipper_format_file_alloc(storage);
success = flipper_format_file_open_existing(ff, brute_force->db_filename);
if(success) {
string_t signal_name;
string_init(signal_name);
while(flipper_format_read_string(ff, "name", signal_name)) {
InfraredBruteForceRecord* record =
InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
if(record) {
++(record->count);
}
}
string_clear(signal_name);
}
flipper_format_free(ff);
furi_record_close("storage");
return success;
}
bool infrared_brute_force_start(
InfraredBruteForce* brute_force,
uint32_t index,
uint32_t* record_count) {
bool success = false;
*record_count = 0;
InfraredBruteForceRecordDict_it_t it;
for(InfraredBruteForceRecordDict_it(it, brute_force->records);
!InfraredBruteForceRecordDict_end_p(it);
InfraredBruteForceRecordDict_next(it)) {
const InfraredBruteForceRecordDict_itref_t* record = InfraredBruteForceRecordDict_cref(it);
if(record->value.index == index) {
*record_count = record->value.count;
if(*record_count) {
string_set(brute_force->current_record_name, record->key);
}
break;
}
}
if(*record_count) {
Storage* storage = furi_record_open("storage");
brute_force->ff = flipper_format_file_alloc(storage);
success = flipper_format_file_open_existing(brute_force->ff, brute_force->db_filename);
if(!success) {
flipper_format_free(brute_force->ff);
furi_record_close("storage");
}
}
return success;
}
bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) {
return brute_force->ff;
}
void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
furi_assert(string_size(brute_force->current_record_name));
furi_assert(brute_force->ff);
string_reset(brute_force->current_record_name);
flipper_format_free(brute_force->ff);
furi_record_close("storage");
brute_force->ff = NULL;
}
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
furi_assert(string_size(brute_force->current_record_name));
furi_assert(brute_force->ff);
bool success = false;
string_t signal_name;
string_init(signal_name);
InfraredSignal* signal = infrared_signal_alloc();
do {
success = infrared_signal_read(signal, brute_force->ff, signal_name);
} while(success && !string_equal_p(brute_force->current_record_name, signal_name));
if(success) {
infrared_signal_transmit(signal);
}
infrared_signal_free(signal);
string_clear(signal_name);
return success;
}
void infrared_brute_force_add_record(
InfraredBruteForce* brute_force,
uint32_t index,
const char* name) {
InfraredBruteForceRecord value = {.index = index, .count = 0};
string_t key;
string_init_set_str(key, name);
InfraredBruteForceRecordDict_set_at(brute_force->records, key, value);
string_clear(key);
}

View file

@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
typedef struct InfraredBruteForce InfraredBruteForce;
InfraredBruteForce* infrared_brute_force_alloc();
void infrared_brute_force_free(InfraredBruteForce* brute_force);
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename);
bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force);
bool infrared_brute_force_start(
InfraredBruteForce* brute_force,
uint32_t index,
uint32_t* record_count);
bool infrared_brute_force_is_started(InfraredBruteForce* brute_force);
void infrared_brute_force_stop(InfraredBruteForce* brute_force);
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force);
void infrared_brute_force_add_record(
InfraredBruteForce* brute_force,
uint32_t index,
const char* name);

View file

@ -1,16 +1,12 @@
#include <furi_hal_delay.h>
#include <infrared.h>
#include <cli/cli.h>
#include <cmsis_os2.h>
#include <infrared_worker.h>
#include <furi.h>
#include <furi_hal_infrared.h>
#include <sstream>
#include <string>
#include <m-string.h>
#include <infrared_transmit.h>
#include <sys/types.h>
#include "../helpers/infrared_parser.h"
#include <cli/cli.h>
#include <infrared.h>
#include <infrared_worker.h>
#include <furi_hal_infrared.h>
#include "infrared_signal.h"
#define INFRARED_CLI_BUF_SIZE 10
static void infrared_cli_start_ir_rx(Cli* cli, string_t args);
static void infrared_cli_start_ir_tx(Cli* cli, string_t args);
@ -92,79 +88,83 @@ static void infrared_cli_print_usage(void) {
INFRARED_MAX_FREQUENCY);
}
static bool parse_message(const char* str, InfraredMessage* message) {
static bool infrared_cli_parse_message(const char* str, InfraredSignal* signal) {
char protocol_name[32];
int parsed = sscanf(str, "%31s %lX %lX", protocol_name, &message->address, &message->command);
InfraredMessage message;
int parsed = sscanf(str, "%31s %lX %lX", protocol_name, &message.address, &message.command);
if(parsed != 3) {
return false;
}
message->protocol = infrared_get_protocol_by_name(protocol_name);
message->repeat = false;
return infrared_parser_is_parsed_signal_valid(message);
message.repeat = false;
infrared_signal_set_message(signal, &message);
return infrared_signal_is_valid(signal);
}
static bool parse_signal_raw(
const char* str,
uint32_t* timings,
uint32_t* timings_cnt,
float* duty_cycle,
uint32_t* frequency) {
char frequency_str[10];
char duty_cycle_str[10];
static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) {
char frequency_str[INFRARED_CLI_BUF_SIZE];
char duty_cycle_str[INFRARED_CLI_BUF_SIZE];
int parsed = sscanf(str, "RAW F:%9s DC:%9s", frequency_str, duty_cycle_str);
if(parsed != 2) return false;
*frequency = atoi(frequency_str);
*duty_cycle = (float)atoi(duty_cycle_str) / 100;
str += strlen(frequency_str) + strlen(duty_cycle_str) + 10;
if(parsed != 2) {
return false;
}
uint32_t timings_cnt_max = *timings_cnt;
*timings_cnt = 0;
uint32_t* timings = malloc(sizeof(uint32_t) * MAX_TIMINGS_AMOUNT);
uint32_t frequency = atoi(frequency_str);
float duty_cycle = (float)atoi(duty_cycle_str) / 100;
str += strlen(frequency_str) + strlen(duty_cycle_str) + INFRARED_CLI_BUF_SIZE;
size_t timings_size = 0;
while(1) {
char timing_str[10];
for(; *str == ' '; ++str)
;
if(1 != sscanf(str, "%9s", timing_str)) break;
while(*str == ' ') {
++str;
}
char timing_str[INFRARED_CLI_BUF_SIZE];
if(sscanf(str, "%9s", timing_str) != 1) {
break;
}
str += strlen(timing_str);
uint32_t timing = atoi(timing_str);
if(timing <= 0) break;
if(*timings_cnt >= timings_cnt_max) break;
timings[*timings_cnt] = timing;
++*timings_cnt;
if((timing <= 0) || (timings_size >= MAX_TIMINGS_AMOUNT)) {
break;
}
return infrared_parser_is_raw_signal_valid(*frequency, *duty_cycle, *timings_cnt);
timings[timings_size] = timing;
++timings_size;
}
infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle);
free(timings);
return infrared_signal_is_valid(signal);
}
static void infrared_cli_start_ir_tx(Cli* cli, string_t args) {
UNUSED(cli);
InfraredMessage message;
const char* str = string_get_cstr(args);
uint32_t frequency;
float duty_cycle;
uint32_t timings_cnt = MAX_TIMINGS_AMOUNT;
uint32_t* timings = (uint32_t*)malloc(sizeof(uint32_t) * timings_cnt);
InfraredSignal* signal = infrared_signal_alloc();
if(parse_message(str, &message)) {
infrared_send(&message, 1);
} else if(parse_signal_raw(str, timings, &timings_cnt, &duty_cycle, &frequency)) {
infrared_send_raw_ext(timings, timings_cnt, true, frequency, duty_cycle);
bool success = infrared_cli_parse_message(str, signal) || infrared_cli_parse_raw(str, signal);
if(success) {
infrared_signal_transmit(signal);
} else {
printf("Wrong arguments.\r\n");
infrared_cli_print_usage();
}
free(timings);
infrared_signal_free(signal);
}
static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) {
UNUSED(context);
if(furi_hal_infrared_is_busy()) {
printf("INFRARED is busy. Exit.");
printf("INFRARED is busy. Exiting.");
return;
}
@ -189,8 +189,7 @@ static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) {
infrared_cli_print_usage();
}
}
extern "C" void infrared_on_system_start() {
void infrared_on_system_start() {
#ifdef SRV_CLI
Cli* cli = (Cli*)furi_record_open("cli");
cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir, NULL);

View file

@ -0,0 +1,51 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
enum InfraredCustomEventType {
// Reserve first 100 events for button types and indexes, starting from 0
InfraredCustomEventTypeReserved = 100,
InfraredCustomEventTypeMenuSelected,
InfraredCustomEventTypeTransmitStarted,
InfraredCustomEventTypeTransmitStopped,
InfraredCustomEventTypeSignalReceived,
InfraredCustomEventTypeTextEditDone,
InfraredCustomEventTypePopupTimeout,
InfraredCustomEventTypeButtonSelected,
InfraredCustomEventTypeBackPressed,
};
#pragma pack(push, 1)
typedef union {
uint32_t packed_value;
struct {
uint16_t type;
int16_t value;
} content;
} InfraredCustomEvent;
#pragma pack(pop)
static inline uint32_t infrared_custom_event_pack(uint16_t type, int16_t value) {
InfraredCustomEvent event = {.content = {.type = type, .value = value}};
return event.packed_value;
}
static inline void
infrared_custom_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) {
InfraredCustomEvent event = {.packed_value = packed_value};
if(type) *type = event.content.type;
if(value) *value = event.content.value;
}
static inline uint16_t infrared_custom_event_get_type(uint32_t packed_value) {
uint16_t type;
infrared_custom_event_unpack(packed_value, &type, NULL);
return type;
}
static inline int16_t infrared_custom_event_get_value(uint32_t packed_value) {
int16_t value;
infrared_custom_event_unpack(packed_value, NULL, &value);
return value;
}

View file

@ -0,0 +1,132 @@
#pragma once
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_stack.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/popup.h>
#include <gui/modules/loading.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include <gui/modules/text_input.h>
#include <gui/modules/button_menu.h>
#include <gui/modules/button_panel.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include <notification/notification_messages.h>
#include <infrared_worker.h>
#include "infrared.h"
#include "infrared_remote.h"
#include "infrared_brute_force.h"
#include "infrared_custom_event.h"
#include "scenes/infrared_scene.h"
#include "views/infrared_progress_view.h"
#include "views/infrared_debug_view.h"
#define INFRARED_FILE_NAME_SIZE 100
#define INFRARED_TEXT_STORE_NUM 2
#define INFRARED_TEXT_STORE_SIZE 128
#define INFRARED_MAX_BUTTON_NAME_LENGTH 22
#define INFRARED_MAX_REMOTE_NAME_LENGTH 22
#define INFRARED_APP_FOLDER "/any/infrared"
#define INFRARED_APP_EXTENSION ".ir"
#define INFRARED_DEFAULT_REMOTE_NAME "Remote"
typedef enum {
InfraredButtonIndexNone = -1,
} InfraredButtonIndex;
typedef enum {
InfraredEditTargetNone,
InfraredEditTargetRemote,
InfraredEditTargetButton,
} InfraredEditTarget;
typedef enum {
InfraredEditModeNone,
InfraredEditModeRename,
InfraredEditModeDelete,
} InfraredEditMode;
typedef struct {
bool is_learning_new_remote;
bool is_debug_enabled;
InfraredEditTarget edit_target : 8;
InfraredEditMode edit_mode : 8;
int32_t current_button_index;
} InfraredAppState;
struct Infrared {
SceneManager* scene_manager;
ViewDispatcher* view_dispatcher;
Gui* gui;
Storage* storage;
DialogsApp* dialogs;
NotificationApp* notifications;
InfraredWorker* worker;
InfraredRemote* remote;
InfraredSignal* received_signal;
InfraredBruteForce* brute_force;
Submenu* submenu;
TextInput* text_input;
DialogEx* dialog_ex;
ButtonMenu* button_menu;
Popup* popup;
ViewStack* view_stack;
InfraredDebugView* debug_view;
ButtonPanel* button_panel;
Loading* loading;
InfraredProgressView* progress;
string_t file_path;
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
InfraredAppState app_state;
};
typedef enum {
InfraredViewSubmenu,
InfraredViewTextInput,
InfraredViewDialogEx,
InfraredViewButtonMenu,
InfraredViewPopup,
InfraredViewStack,
InfraredViewDebugView,
} InfraredView;
typedef enum {
InfraredNotificationMessageSuccess,
InfraredNotificationMessageGreenOn,
InfraredNotificationMessageGreenOff,
InfraredNotificationMessageBlinkRead,
InfraredNotificationMessageBlinkSend,
} InfraredNotificationMessage;
bool infrared_add_remote_with_button(Infrared* infrared, const char* name, InfraredSignal* signal);
bool infrared_rename_current_remote(Infrared* infrared, const char* name);
void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal);
void infrared_tx_start_button_index(Infrared* infrared, size_t button_index);
void infrared_tx_start_received(Infrared* infrared);
void infrared_tx_stop(Infrared* infrared);
void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...);
void infrared_text_store_clear(Infrared* infrared, uint32_t bank);
void infrared_play_notification_message(Infrared* infrared, uint32_t message);
void infrared_show_loading_popup(Infrared* infrared, bool show);
void infrared_signal_sent_callback(void* context);
void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal);
void infrared_text_input_callback(void* context);
void infrared_popup_timeout_callback(void* context);

View file

@ -0,0 +1,176 @@
#include "infrared_remote.h"
#include <stdlib.h>
#include <m-string.h>
#include <m-array.h>
#include <toolbox/path.h>
#include <storage/storage.h>
#include <furi/common_defines.h>
#define TAG "InfraredRemote"
ARRAY_DEF(InfraredButtonArray, InfraredRemoteButton*, M_PTR_OPLIST);
struct InfraredRemote {
InfraredButtonArray_t buttons;
string_t name;
string_t path;
};
static void infrared_remote_clear_buttons(InfraredRemote* remote) {
InfraredButtonArray_it_t it;
for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it);
InfraredButtonArray_next(it)) {
infrared_remote_button_free(*InfraredButtonArray_cref(it));
}
InfraredButtonArray_reset(remote->buttons);
}
InfraredRemote* infrared_remote_alloc() {
InfraredRemote* remote = malloc(sizeof(InfraredRemote));
InfraredButtonArray_init(remote->buttons);
string_init(remote->name);
string_init(remote->path);
return remote;
}
void infrared_remote_free(InfraredRemote* remote) {
infrared_remote_clear_buttons(remote);
InfraredButtonArray_clear(remote->buttons);
string_clear(remote->path);
string_clear(remote->name);
free(remote);
}
void infrared_remote_reset(InfraredRemote* remote) {
infrared_remote_clear_buttons(remote);
string_reset(remote->name);
string_reset(remote->path);
}
void infrared_remote_set_name(InfraredRemote* remote, const char* name) {
string_set_str(remote->name, name);
}
const char* infrared_remote_get_name(InfraredRemote* remote) {
return string_get_cstr(remote->name);
}
void infrared_remote_set_path(InfraredRemote* remote, const char* path) {
string_set_str(remote->path, path);
}
const char* infrared_remote_get_path(InfraredRemote* remote) {
return string_get_cstr(remote->path);
}
size_t infrared_remote_get_button_count(InfraredRemote* remote) {
return InfraredButtonArray_size(remote->buttons);
}
InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index) {
furi_assert(index < InfraredButtonArray_size(remote->buttons));
return *InfraredButtonArray_get(remote->buttons, index);
}
bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) {
InfraredRemoteButton* button = infrared_remote_button_alloc();
infrared_remote_button_set_name(button, name);
infrared_remote_button_set_signal(button, signal);
InfraredButtonArray_push_back(remote->buttons, button);
return infrared_remote_store(remote);
}
bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index) {
furi_assert(index < InfraredButtonArray_size(remote->buttons));
InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, index);
infrared_remote_button_set_name(button, new_name);
return infrared_remote_store(remote);
}
bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) {
furi_assert(index < InfraredButtonArray_size(remote->buttons));
InfraredRemoteButton* button;
InfraredButtonArray_pop_at(&button, remote->buttons, index);
infrared_remote_button_free(button);
return infrared_remote_store(remote);
}
bool infrared_remote_store(InfraredRemote* remote) {
Storage* storage = furi_record_open("storage");
FlipperFormat* ff = flipper_format_file_alloc(storage);
const char* path = string_get_cstr(remote->path);
FURI_LOG_I(TAG, "store file: \'%s\'", path);
bool success = flipper_format_file_open_always(ff, path) &&
flipper_format_write_header_cstr(ff, "IR signals file", 1);
if(success) {
InfraredButtonArray_it_t it;
for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it);
InfraredButtonArray_next(it)) {
InfraredRemoteButton* button = *InfraredButtonArray_cref(it);
success = infrared_signal_save(
infrared_remote_button_get_signal(button),
ff,
infrared_remote_button_get_name(button));
if(!success) {
break;
}
}
}
flipper_format_free(ff);
furi_record_close("storage");
return success;
}
bool infrared_remote_load(InfraredRemote* remote, string_t path) {
Storage* storage = furi_record_open("storage");
FlipperFormat* ff = flipper_format_file_alloc(storage);
string_t buf;
string_init(buf);
FURI_LOG_I(TAG, "load file: \'%s\'", string_get_cstr(path));
bool success = flipper_format_file_open_existing(ff, string_get_cstr(path));
if(success) {
uint32_t version;
success = flipper_format_read_header(ff, buf, &version) &&
!string_cmp_str(buf, "IR signals file") && (version == 1);
}
if(success) {
path_extract_filename(path, buf, true);
infrared_remote_clear_buttons(remote);
infrared_remote_set_name(remote, string_get_cstr(buf));
infrared_remote_set_path(remote, string_get_cstr(path));
for(bool can_read = true; can_read;) {
InfraredRemoteButton* button = infrared_remote_button_alloc();
can_read = infrared_signal_read(infrared_remote_button_get_signal(button), ff, buf);
if(can_read) {
infrared_remote_button_set_name(button, string_get_cstr(buf));
InfraredButtonArray_push_back(remote->buttons, button);
} else {
infrared_remote_button_free(button);
}
}
}
string_clear(buf);
flipper_format_free(ff);
furi_record_close("storage");
return success;
}
bool infrared_remote_remove(InfraredRemote* remote) {
Storage* storage = furi_record_open("storage");
FS_Error status = storage_common_remove(storage, string_get_cstr(remote->path));
infrared_remote_reset(remote);
furi_record_close("storage");
return (status == FSE_OK || status == FSE_NOT_EXIST);
}

View file

@ -0,0 +1,28 @@
#pragma once
#include <stdbool.h>
#include "infrared_remote_button.h"
typedef struct InfraredRemote InfraredRemote;
InfraredRemote* infrared_remote_alloc();
void infrared_remote_free(InfraredRemote* remote);
void infrared_remote_reset(InfraredRemote* remote);
void infrared_remote_set_name(InfraredRemote* remote, const char* name);
const char* infrared_remote_get_name(InfraredRemote* remote);
void infrared_remote_set_path(InfraredRemote* remote, const char* path);
const char* infrared_remote_get_path(InfraredRemote* remote);
size_t infrared_remote_get_button_count(InfraredRemote* remote);
InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index);
bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal);
bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index);
bool infrared_remote_delete_button(InfraredRemote* remote, size_t index);
bool infrared_remote_store(InfraredRemote* remote);
bool infrared_remote_load(InfraredRemote* remote, string_t path);
bool infrared_remote_remove(InfraredRemote* remote);

View file

@ -0,0 +1,38 @@
#include "infrared_remote_button.h"
#include <stdlib.h>
#include <m-string.h>
struct InfraredRemoteButton {
string_t name;
InfraredSignal* signal;
};
InfraredRemoteButton* infrared_remote_button_alloc() {
InfraredRemoteButton* button = malloc(sizeof(InfraredRemoteButton));
string_init(button->name);
button->signal = infrared_signal_alloc();
return button;
}
void infrared_remote_button_free(InfraredRemoteButton* button) {
string_clear(button->name);
infrared_signal_free(button->signal);
free(button);
}
void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name) {
string_set_str(button->name, name);
}
const char* infrared_remote_button_get_name(InfraredRemoteButton* button) {
return string_get_cstr(button->name);
}
void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal) {
infrared_signal_set_signal(button->signal, signal);
}
InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button) {
return button->signal;
}

View file

@ -0,0 +1,14 @@
#pragma once
#include "infrared_signal.h"
typedef struct InfraredRemoteButton InfraredRemoteButton;
InfraredRemoteButton* infrared_remote_button_alloc();
void infrared_remote_button_free(InfraredRemoteButton* button);
void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name);
const char* infrared_remote_button_get_name(InfraredRemoteButton* button);
void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal);
InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button);

View file

@ -1,9 +0,0 @@
#include "infrared_app.h"
extern "C" int32_t infrared_app(void* p) {
InfraredApp* app = new InfraredApp();
int32_t result = app->run(p);
delete app;
return result;
}

View file

@ -0,0 +1,264 @@
#include "infrared_signal.h"
#include <stdlib.h>
#include <string.h>
#include <furi/check.h>
#include <infrared_worker.h>
#include <infrared_transmit.h>
#define TAG "InfraredSignal"
struct InfraredSignal {
bool is_raw;
union {
InfraredMessage message;
InfraredRawSignal raw;
} payload;
};
static void infrared_signal_clear_timings(InfraredSignal* signal) {
if(signal->is_raw) {
free(signal->payload.raw.timings);
signal->payload.raw.timings_size = 0;
signal->payload.raw.timings = NULL;
}
}
static bool infrared_signal_is_message_valid(InfraredMessage* message) {
if(!infrared_is_protocol_valid(message->protocol)) {
FURI_LOG_E(TAG, "Unknown protocol");
return false;
}
uint32_t address_length = infrared_get_protocol_address_length(message->protocol);
uint32_t address_mask = (1UL << address_length) - 1;
if(message->address != (message->address & address_mask)) {
FURI_LOG_E(
TAG,
"Address is out of range (mask 0x%08lX): 0x%lX\r\n",
address_mask,
message->address);
return false;
}
uint32_t command_length = infrared_get_protocol_command_length(message->protocol);
uint32_t command_mask = (1UL << command_length) - 1;
if(message->command != (message->command & command_mask)) {
FURI_LOG_E(
TAG,
"Command is out of range (mask 0x%08lX): 0x%lX\r\n",
command_mask,
message->command);
return false;
}
return true;
}
static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) {
if((raw->frequency > INFRARED_MAX_FREQUENCY) || (raw->frequency < INFRARED_MIN_FREQUENCY)) {
FURI_LOG_E(
TAG,
"Frequency is out of range (%lX - %lX): %lX",
INFRARED_MIN_FREQUENCY,
INFRARED_MAX_FREQUENCY,
raw->frequency);
return false;
} else if((raw->duty_cycle <= 0) || (raw->duty_cycle > 1)) {
FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)raw->duty_cycle);
return false;
} else if((raw->timings_size <= 0) || (raw->timings_size > MAX_TIMINGS_AMOUNT)) {
FURI_LOG_E(
TAG,
"Timings amount is out of range (0 - %lX): %lX",
MAX_TIMINGS_AMOUNT,
raw->timings_size);
return false;
}
return true;
}
static inline bool infrared_signal_save_message(InfraredMessage* message, FlipperFormat* ff) {
const char* protocol_name = infrared_get_protocol_name(message->protocol);
return flipper_format_write_string_cstr(ff, "type", "parsed") &&
flipper_format_write_string_cstr(ff, "protocol", protocol_name) &&
flipper_format_write_hex(ff, "address", (uint8_t*)&message->address, 4) &&
flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4);
}
static inline bool infrared_signal_save_raw(InfraredRawSignal* raw, FlipperFormat* ff) {
furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT);
return flipper_format_write_string_cstr(ff, "type", "raw") &&
flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) &&
flipper_format_write_float(ff, "duty_cycle", &raw->duty_cycle, 1) &&
flipper_format_write_uint32(ff, "data", raw->timings, raw->timings_size);
}
static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) {
string_t buf;
string_init(buf);
bool success = false;
do {
if(!flipper_format_read_string(ff, "protocol", buf)) break;
InfraredMessage message;
message.protocol = infrared_get_protocol_by_name(string_get_cstr(buf));
success = flipper_format_read_hex(ff, "address", (uint8_t*)&message.address, 4) &&
flipper_format_read_hex(ff, "command", (uint8_t*)&message.command, 4) &&
infrared_signal_is_message_valid(&message);
if(!success) break;
infrared_signal_set_message(signal, &message);
} while(0);
string_clear(buf);
return success;
}
static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) {
uint32_t timings_size, frequency;
float duty_cycle;
bool success = flipper_format_read_uint32(ff, "frequency", &frequency, 1) &&
flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1) &&
flipper_format_get_value_count(ff, "data", &timings_size);
if(!success || timings_size > MAX_TIMINGS_AMOUNT) {
return false;
}
uint32_t* timings = malloc(sizeof(uint32_t) * timings_size);
success = flipper_format_read_uint32(ff, "data", timings, timings_size);
if(success) {
infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle);
}
free(timings);
return success;
}
InfraredSignal* infrared_signal_alloc() {
InfraredSignal* signal = malloc(sizeof(InfraredSignal));
signal->is_raw = false;
signal->payload.message.protocol = InfraredProtocolUnknown;
return signal;
}
void infrared_signal_free(InfraredSignal* signal) {
infrared_signal_clear_timings(signal);
free(signal);
}
bool infrared_signal_is_raw(InfraredSignal* signal) {
return signal->is_raw;
}
bool infrared_signal_is_valid(InfraredSignal* signal) {
return signal->is_raw ? infrared_signal_is_raw_valid(&signal->payload.raw) :
infrared_signal_is_message_valid(&signal->payload.message);
}
void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other) {
if(other->is_raw) {
const InfraredRawSignal* raw = &other->payload.raw;
infrared_signal_set_raw_signal(
signal, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle);
} else {
const InfraredMessage* message = &other->payload.message;
infrared_signal_set_message(signal, message);
}
}
void infrared_signal_set_raw_signal(
InfraredSignal* signal,
const uint32_t* timings,
size_t timings_size,
uint32_t frequency,
float duty_cycle) {
infrared_signal_clear_timings(signal);
signal->is_raw = true;
signal->payload.raw.timings_size = timings_size;
signal->payload.raw.frequency = frequency;
signal->payload.raw.duty_cycle = duty_cycle;
signal->payload.raw.timings = malloc(timings_size * sizeof(uint32_t));
memcpy(signal->payload.raw.timings, timings, timings_size * sizeof(uint32_t));
}
InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal) {
furi_assert(signal->is_raw);
return &signal->payload.raw;
}
void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message) {
infrared_signal_clear_timings(signal);
signal->is_raw = false;
signal->payload.message = *message;
}
InfraredMessage* infrared_signal_get_message(InfraredSignal* signal) {
furi_assert(!signal->is_raw);
return &signal->payload.message;
}
bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name) {
if(!flipper_format_write_comment_cstr(ff, "") ||
!flipper_format_write_string_cstr(ff, "name", name)) {
return false;
} else if(signal->is_raw) {
return infrared_signal_save_raw(&signal->payload.raw, ff);
} else {
return infrared_signal_save_message(&signal->payload.message, ff);
}
}
bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name) {
string_t buf;
string_init(buf);
bool success = false;
do {
if(!flipper_format_read_string(ff, "name", buf)) break;
string_set(name, buf);
if(!flipper_format_read_string(ff, "type", buf)) break;
if(!string_cmp_str(buf, "raw")) {
success = infrared_signal_read_raw(signal, ff);
} else if(!string_cmp_str(buf, "parsed")) {
success = infrared_signal_read_message(signal, ff);
} else {
FURI_LOG_E(TAG, "Unknown type of signal (allowed - raw/parsed) ");
}
} while(0);
string_clear(buf);
return success;
}
void infrared_signal_transmit(InfraredSignal* signal) {
if(signal->is_raw) {
InfraredRawSignal* raw_signal = &signal->payload.raw;
infrared_send_raw_ext(
raw_signal->timings,
raw_signal->timings_size,
true,
raw_signal->frequency,
raw_signal->duty_cycle);
} else {
InfraredMessage* message = &signal->payload.message;
infrared_send(message, 1);
}
}

View file

@ -0,0 +1,41 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <infrared.h>
#include <flipper_format/flipper_format.h>
typedef struct InfraredSignal InfraredSignal;
typedef struct {
size_t timings_size;
uint32_t* timings;
uint32_t frequency;
float duty_cycle;
} InfraredRawSignal;
InfraredSignal* infrared_signal_alloc();
void infrared_signal_free(InfraredSignal* signal);
bool infrared_signal_is_raw(InfraredSignal* signal);
bool infrared_signal_is_valid(InfraredSignal* signal);
void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other);
void infrared_signal_set_raw_signal(
InfraredSignal* signal,
const uint32_t* timings,
size_t timings_size,
uint32_t frequency,
float duty_cycle);
InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal);
void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message);
InfraredMessage* infrared_signal_get_message(InfraredSignal* signal);
bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name);
bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name);
void infrared_signal_transmit(InfraredSignal* signal);

View file

@ -1,305 +0,0 @@
/**
* @file infrared_app_scene.h
* Infrared: Application scenes
*/
#pragma once
#include "../infrared_app_event.h"
#include <furi_hal_infrared.h>
#include "infrared.h"
#include <vector>
#include <string>
#include "../infrared_app_brute_force.h"
/** Anonymous class */
class InfraredApp;
/** Base Scene class */
class InfraredAppScene {
public:
/** Called when enter scene */
virtual void on_enter(InfraredApp* app) = 0;
/** Events handler callback */
virtual bool on_event(InfraredApp* app, InfraredAppEvent* event) = 0;
/** Called when exit scene */
virtual void on_exit(InfraredApp* app) = 0;
/** Virtual destructor of base class */
virtual ~InfraredAppScene(){};
private:
};
/** Start scene
* Main Infrared application menu
*/
class InfraredAppSceneStart : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
private:
/** Save previously selected submenu index
* to highlight it when get back */
uint32_t submenu_item_selected = 0;
};
/** Universal menu scene
* Scene to select universal remote
*/
class InfraredAppSceneUniversal : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
private:
/** Save previously selected submenu index
* to highlight it when get back */
uint32_t submenu_item_selected = 0;
};
/** Learn new signal scene
* On this scene catching new IR signal performed.
*/
class InfraredAppSceneLearn : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
};
/** New signal learn succeeded scene
*/
class InfraredAppSceneLearnSuccess : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
bool button_pressed = false;
};
/** Scene to enter name for new button in remote
*/
class InfraredAppSceneLearnEnterName : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
};
/** Scene where signal is learnt
*/
class InfraredAppSceneLearnDone : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
};
/** Remote interface scene
* On this scene you can send IR signals from selected remote
*/
class InfraredAppSceneRemote : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
private:
/** container of button names in current remote. */
std::vector<std::string> buttons_names;
/** Save previously selected index
* to highlight it when get back */
uint32_t buttonmenu_item_selected = 0;
/** state flag to show button is pressed.
* As long as send-signal button pressed no other button
* events are handled. */
bool button_pressed = false;
};
/** List of remotes scene
* Every remote is a file, located on internal/external storage.
* Every file has same format, and same extension.
* Files are parsed as you enter 'Remote scene' and showed
* as a buttons.
*/
class InfraredAppSceneRemoteList : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
private:
/** Save previously selected index
* to highlight it when get back */
uint32_t submenu_item_selected = 0;
/** Remote names to show them in submenu */
std::vector<std::string> remote_names;
};
class InfraredAppSceneAskBack : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
};
class InfraredAppSceneEdit : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
private:
/** Save previously selected index
* to highlight it when get back */
uint32_t submenu_item_selected = 0;
};
class InfraredAppSceneEditKeySelect : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
private:
/** Button names to show them in submenu */
std::vector<std::string> buttons_names;
};
class InfraredAppSceneEditRename : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
};
class InfraredAppSceneEditDelete : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
};
class InfraredAppSceneEditRenameDone : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
};
class InfraredAppSceneEditDeleteDone : public InfraredAppScene {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
};
class InfraredAppSceneUniversalCommon : public InfraredAppScene {
/** Brute force started flag */
bool brute_force_started = false;
protected:
/** Events handler callback */
bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
/** Called when exit scene */
void on_exit(InfraredApp* app) final;
/** Show popup window
*
* @param app - application instance
*/
void show_popup(InfraredApp* app, int record_amount);
/** Hide popup window
*
* @param app - application instance
*/
void hide_popup(InfraredApp* app);
/** Propagate progress in popup window
*
* @param app - application instance
*/
bool progress_popup(InfraredApp* app);
/** Item selected callback
*
* @param context - context
* @param index - selected item index
*/
static void infrared_app_item_callback(void* context, uint32_t index);
/** Brute Force instance */
InfraredAppBruteForce brute_force;
/** Constructor */
InfraredAppSceneUniversalCommon(const char* filename)
: brute_force(filename) {
}
/** Destructor */
~InfraredAppSceneUniversalCommon() {
}
};
class InfraredAppSceneUniversalTV : public InfraredAppSceneUniversalCommon {
public:
/** Called when enter scene */
void on_enter(InfraredApp* app) final;
/** Constructor
* Specifies path to brute force db library */
InfraredAppSceneUniversalTV()
: InfraredAppSceneUniversalCommon("/ext/infrared/assets/tv.ir") {
}
/** Destructor */
~InfraredAppSceneUniversalTV() {
}
};

View file

@ -1,73 +0,0 @@
#include "../infrared_app.h"
#include "gui/modules/dialog_ex.h"
#include "infrared.h"
#include "infrared/scene/infrared_app_scene.h"
#include <string>
static void dialog_result_callback(DialogExResult result, void* context) {
auto app = static_cast<InfraredApp*>(context);
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::DialogExSelected;
event.payload.dialog_ex_result = result;
app->get_view_manager()->send_event(&event);
}
void InfraredAppSceneAskBack::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
DialogEx* dialog_ex = view_manager->get_dialog_ex();
if(app->get_learn_new_remote()) {
dialog_ex_set_header(dialog_ex, "Exit to Infrared menu?", 64, 0, AlignCenter, AlignTop);
} else {
dialog_ex_set_header(dialog_ex, "Exit to remote menu?", 64, 0, AlignCenter, AlignTop);
}
dialog_ex_set_text(
dialog_ex, "All unsaved data\nwill be lost", 64, 31, AlignCenter, AlignCenter);
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
dialog_ex_set_left_button_text(dialog_ex, "Exit");
dialog_ex_set_center_button_text(dialog_ex, nullptr);
dialog_ex_set_right_button_text(dialog_ex, "Stay");
dialog_ex_set_result_callback(dialog_ex, dialog_result_callback);
dialog_ex_set_context(dialog_ex, app);
view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx);
}
bool InfraredAppSceneAskBack::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::Type::DialogExSelected) {
switch(event->payload.dialog_ex_result) {
case DialogExResultLeft:
consumed = true;
if(app->get_learn_new_remote()) {
app->search_and_switch_to_previous_scene({InfraredApp::Scene::Start});
} else {
app->search_and_switch_to_previous_scene(
{InfraredApp::Scene::Edit, InfraredApp::Scene::Remote});
}
break;
case DialogExResultCenter:
furi_assert(0);
break;
case DialogExResultRight:
app->switch_to_previous_scene();
consumed = true;
break;
default:
break;
}
}
if(event->type == InfraredAppEvent::Type::Back) {
consumed = true;
}
return consumed;
}
void InfraredAppSceneAskBack::on_exit(InfraredApp*) {
}

View file

@ -1,79 +0,0 @@
#include "../infrared_app.h"
#include "gui/modules/submenu.h"
typedef enum {
SubmenuIndexAddKey,
SubmenuIndexRenameKey,
SubmenuIndexDeleteKey,
SubmenuIndexRenameRemote,
SubmenuIndexDeleteRemote,
} SubmenuIndex;
static void submenu_callback(void* context, uint32_t index) {
InfraredApp* app = static_cast<InfraredApp*>(context);
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::MenuSelected;
event.payload.menu_index = index;
app->get_view_manager()->send_event(&event);
}
void InfraredAppSceneEdit::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Submenu* submenu = view_manager->get_submenu();
submenu_add_item(submenu, "Add Button", SubmenuIndexAddKey, submenu_callback, app);
submenu_add_item(submenu, "Rename Button", SubmenuIndexRenameKey, submenu_callback, app);
submenu_add_item(submenu, "Delete Button", 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(InfraredAppViewManager::ViewId::Submenu);
}
bool InfraredAppSceneEdit::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::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(InfraredApp::Scene::Learn);
break;
case SubmenuIndexRenameKey:
app->set_edit_action(InfraredApp::EditAction::Rename);
app->set_edit_element(InfraredApp::EditElement::Button);
app->switch_to_next_scene(InfraredApp::Scene::EditKeySelect);
break;
case SubmenuIndexDeleteKey:
app->set_edit_action(InfraredApp::EditAction::Delete);
app->set_edit_element(InfraredApp::EditElement::Button);
app->switch_to_next_scene(InfraredApp::Scene::EditKeySelect);
break;
case SubmenuIndexRenameRemote:
app->set_edit_action(InfraredApp::EditAction::Rename);
app->set_edit_element(InfraredApp::EditElement::Remote);
app->switch_to_next_scene(InfraredApp::Scene::EditRename);
break;
case SubmenuIndexDeleteRemote:
app->set_edit_action(InfraredApp::EditAction::Delete);
app->set_edit_element(InfraredApp::EditElement::Remote);
app->switch_to_next_scene(InfraredApp::Scene::EditDelete);
break;
}
consumed = true;
}
return consumed;
}
void InfraredAppSceneEdit::on_exit(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Submenu* submenu = view_manager->get_submenu();
submenu_reset(submenu);
}

View file

@ -1,100 +0,0 @@
#include "../infrared_app.h"
#include "infrared.h"
#include "infrared/scene/infrared_app_scene.h"
#include <string>
static void dialog_result_callback(DialogExResult result, void* context) {
auto app = static_cast<InfraredApp*>(context);
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::DialogExSelected;
event.payload.dialog_ex_result = result;
app->get_view_manager()->send_event(&event);
}
void InfraredAppSceneEditDelete::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
DialogEx* dialog_ex = view_manager->get_dialog_ex();
auto remote_manager = app->get_remote_manager();
if(app->get_edit_element() == InfraredApp::EditElement::Button) {
auto signal = remote_manager->get_button_data(app->get_current_button());
dialog_ex_set_header(dialog_ex, "Delete button?", 64, 0, AlignCenter, AlignTop);
if(!signal.is_raw()) {
auto message = &signal.get_message();
app->set_text_store(
0,
"%s\n%s\nA=0x%0*lX C=0x%0*lX",
remote_manager->get_button_name(app->get_current_button()).c_str(),
infrared_get_protocol_name(message->protocol),
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
message->address,
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
message->command);
} else {
app->set_text_store(
0,
"%s\nRAW\n%ld samples",
remote_manager->get_button_name(app->get_current_button()).c_str(),
signal.get_raw_signal().timings_cnt);
}
} else {
dialog_ex_set_header(dialog_ex, "Delete remote?", 64, 0, AlignCenter, AlignTop);
app->set_text_store(
0,
"%s\n with %lu buttons",
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, 31, AlignCenter, AlignCenter);
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
dialog_ex_set_right_button_text(dialog_ex, "Delete");
dialog_ex_set_result_callback(dialog_ex, dialog_result_callback);
dialog_ex_set_context(dialog_ex, app);
view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx);
}
bool InfraredAppSceneEditDelete::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::Type::DialogExSelected) {
switch(event->payload.dialog_ex_result) {
case DialogExResultLeft:
app->switch_to_previous_scene();
break;
case DialogExResultCenter:
furi_assert(0);
break;
case DialogExResultRight: {
auto remote_manager = app->get_remote_manager();
bool result = false;
if(app->get_edit_element() == InfraredApp::EditElement::Remote) {
result = remote_manager->delete_remote();
} else {
result = remote_manager->delete_button(app->get_current_button());
app->set_current_button(InfraredApp::ButtonNA);
}
if(!result) {
app->search_and_switch_to_previous_scene(
{InfraredApp::Scene::RemoteList, InfraredApp::Scene::Start});
} else {
app->switch_to_next_scene(InfraredApp::Scene::EditDeleteDone);
}
break;
}
default:
break;
}
}
return consumed;
}
void InfraredAppSceneEditDelete::on_exit(InfraredApp*) {
}

View file

@ -1,38 +0,0 @@
#include "../infrared_app.h"
void InfraredAppSceneEditDeleteDone::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Popup* popup = view_manager->get_popup();
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
popup_set_callback(popup, InfraredApp::popup_callback);
popup_set_context(popup, app);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_manager->switch_to(InfraredAppViewManager::ViewId::Popup);
}
bool InfraredAppSceneEditDeleteDone::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::Type::PopupTimer) {
if(app->get_edit_element() == InfraredApp::EditElement::Remote) {
app->search_and_switch_to_previous_scene(
{InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList});
} else {
app->search_and_switch_to_previous_scene({InfraredApp::Scene::Remote});
}
consumed = true;
}
return consumed;
}
void InfraredAppSceneEditDeleteDone::on_exit(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Popup* popup = view_manager->get_popup();
popup_set_header(popup, nullptr, 0, 0, AlignLeft, AlignTop);
}

View file

@ -1,58 +0,0 @@
#include "../infrared_app.h"
#include "gui/modules/submenu.h"
static void submenu_callback(void* context, uint32_t index) {
InfraredApp* app = static_cast<InfraredApp*>(context);
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::MenuSelected;
event.payload.menu_index = index;
app->get_view_manager()->send_event(&event);
}
void InfraredAppSceneEditKeySelect::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Submenu* submenu = view_manager->get_submenu();
int item_number = 0;
const char* header = app->get_edit_action() == InfraredApp::EditAction::Rename ?
"Rename Button:" :
"Delete Button:";
submenu_set_header(submenu, header);
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(), item_number++, submenu_callback, app);
}
if((item_number > 0) && (app->get_current_button() != InfraredApp::ButtonNA)) {
submenu_set_selected_item(submenu, app->get_current_button());
app->set_current_button(InfraredApp::ButtonNA);
}
view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu);
}
bool InfraredAppSceneEditKeySelect::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::Type::MenuSelected) {
app->set_current_button(event->payload.menu_index);
consumed = true;
if(app->get_edit_action() == InfraredApp::EditAction::Rename) {
app->switch_to_next_scene(InfraredApp::Scene::EditRename);
} else {
app->switch_to_next_scene(InfraredApp::Scene::EditDelete);
}
}
return consumed;
}
void InfraredAppSceneEditKeySelect::on_exit(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Submenu* submenu = view_manager->get_submenu();
submenu_reset(submenu);
}

View file

@ -1,83 +0,0 @@
#include "../infrared_app.h"
#include "m-string.h"
#include "toolbox/path.h"
void InfraredAppSceneEditRename::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
TextInput* text_input = view_manager->get_text_input();
size_t enter_name_length = 0;
auto remote_manager = app->get_remote_manager();
if(app->get_edit_element() == InfraredApp::EditElement::Button) {
furi_assert(app->get_current_button() != InfraredApp::ButtonNA);
auto button_name = remote_manager->get_button_name(app->get_current_button());
char* buffer_str = app->get_text_store(0);
size_t max_len = InfraredAppRemoteManager::max_button_name_length;
strncpy(buffer_str, button_name.c_str(), max_len);
buffer_str[max_len + 1] = 0;
enter_name_length = max_len;
text_input_set_header_text(text_input, "Name the button");
} else {
auto remote_name = remote_manager->get_remote_name();
strncpy(app->get_text_store(0), remote_name.c_str(), app->get_text_store_size());
enter_name_length = InfraredAppRemoteManager::max_remote_name_length;
text_input_set_header_text(text_input, "Name the remote");
string_t folder_path;
string_init(folder_path);
if(string_end_with_str_p(app->file_path, InfraredApp::infrared_extension)) {
path_extract_dirname(string_get_cstr(app->file_path), folder_path);
}
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
string_get_cstr(folder_path), app->infrared_extension, remote_name.c_str());
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
string_clear(folder_path);
}
text_input_set_result_callback(
text_input,
InfraredApp::text_input_callback,
app,
app->get_text_store(0),
enter_name_length,
false);
view_manager->switch_to(InfraredAppViewManager::ViewId::TextInput);
}
bool InfraredAppSceneEditRename::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::Type::TextEditDone) {
auto remote_manager = app->get_remote_manager();
bool result = false;
if(app->get_edit_element() == InfraredApp::EditElement::Button) {
result =
remote_manager->rename_button(app->get_current_button(), app->get_text_store(0));
app->set_current_button(InfraredApp::ButtonNA);
} else {
result = remote_manager->rename_remote(app->get_text_store(0));
}
if(!result) {
app->search_and_switch_to_previous_scene(
{InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList});
} else {
app->switch_to_next_scene_without_saving(InfraredApp::Scene::EditRenameDone);
}
consumed = true;
}
return consumed;
}
void InfraredAppSceneEditRename::on_exit(InfraredApp* app) {
TextInput* text_input = app->get_view_manager()->get_text_input();
void* validator_context = text_input_get_validator_callback_context(text_input);
text_input_set_validator(text_input, NULL, NULL);
if(validator_context != NULL) validator_is_file_free((ValidatorIsFile*)validator_context);
}

View file

@ -1,30 +0,0 @@
#include "../infrared_app.h"
void InfraredAppSceneEditRenameDone::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Popup* popup = view_manager->get_popup();
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
popup_set_callback(popup, InfraredApp::popup_callback);
popup_set_context(popup, app);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_manager->switch_to(InfraredAppViewManager::ViewId::Popup);
}
bool InfraredAppSceneEditRenameDone::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::Type::PopupTimer) {
app->switch_to_next_scene(InfraredApp::Scene::Remote);
consumed = true;
}
return consumed;
}
void InfraredAppSceneEditRenameDone::on_exit(InfraredApp*) {
}

View file

@ -1,76 +0,0 @@
#include "../infrared_app.h"
#include "../infrared_app_event.h"
#include "infrared.h"
#include <infrared_worker.h>
static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
furi_assert(context);
furi_assert(received_signal);
InfraredApp* app = static_cast<InfraredApp*>(context);
if(infrared_worker_signal_is_decoded(received_signal)) {
InfraredAppSignal signal(infrared_worker_get_decoded_signal(received_signal));
app->set_received_signal(signal);
} else {
const uint32_t* timings;
size_t timings_cnt;
infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt);
InfraredAppSignal signal(
timings, timings_cnt, INFRARED_COMMON_CARRIER_FREQUENCY, INFRARED_COMMON_DUTY_CYCLE);
app->set_received_signal(signal);
}
infrared_worker_rx_set_received_signal_callback(app->get_infrared_worker(), NULL, NULL);
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::InfraredMessageReceived;
auto view_manager = app->get_view_manager();
view_manager->send_event(&event);
}
void InfraredAppSceneLearn::on_enter(InfraredApp* app) {
auto view_manager = app->get_view_manager();
auto popup = view_manager->get_popup();
auto worker = app->get_infrared_worker();
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, app);
infrared_worker_rx_start(worker);
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);
view_manager->switch_to(InfraredAppViewManager::ViewId::Popup);
}
bool InfraredAppSceneLearn::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
switch(event->type) {
case InfraredAppEvent::Type::Tick:
consumed = true;
app->notify_blink_read();
break;
case InfraredAppEvent::Type::InfraredMessageReceived:
app->notify_success();
app->switch_to_next_scene_without_saving(InfraredApp::Scene::LearnSuccess);
break;
case InfraredAppEvent::Type::Back:
consumed = true;
app->switch_to_previous_scene();
break;
default:
furi_assert(0);
}
return consumed;
}
void InfraredAppSceneLearn::on_exit(InfraredApp* app) {
infrared_worker_rx_stop(app->get_infrared_worker());
auto view_manager = app->get_view_manager();
auto popup = view_manager->get_popup();
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignCenter);
}

View file

@ -1,41 +0,0 @@
#include "../infrared_app.h"
#include <dolphin/dolphin.h>
void InfraredAppSceneLearnDone::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Popup* popup = view_manager->get_popup();
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
DOLPHIN_DEED(DolphinDeedIrSave);
if(app->get_learn_new_remote()) {
popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop);
} else {
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
}
popup_set_callback(popup, InfraredApp::popup_callback);
popup_set_context(popup, app);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_manager->switch_to(InfraredAppViewManager::ViewId::Popup);
}
bool InfraredAppSceneLearnDone::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::Type::PopupTimer) {
app->switch_to_next_scene(InfraredApp::Scene::Remote);
consumed = true;
}
return consumed;
}
void InfraredAppSceneLearnDone::on_exit(InfraredApp* app) {
app->set_learn_new_remote(false);
InfraredAppViewManager* view_manager = app->get_view_manager();
Popup* popup = view_manager->get_popup();
popup_set_header(popup, nullptr, 0, 0, AlignLeft, AlignTop);
}

View file

@ -1,60 +0,0 @@
#include "../infrared_app.h"
#include "gui/modules/text_input.h"
void InfraredAppSceneLearnEnterName::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
TextInput* text_input = view_manager->get_text_input();
auto signal = app->get_received_signal();
if(!signal.is_raw()) {
auto message = &signal.get_message();
app->set_text_store(
0,
"%.4s_%0*lX",
infrared_get_protocol_name(message->protocol),
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
message->command);
} else {
auto raw_signal = signal.get_raw_signal();
app->set_text_store(0, "RAW_%d", raw_signal.timings_cnt);
}
text_input_set_header_text(text_input, "Name the button");
text_input_set_result_callback(
text_input,
InfraredApp::text_input_callback,
app,
app->get_text_store(0),
InfraredAppRemoteManager::max_button_name_length,
true);
view_manager->switch_to(InfraredAppViewManager::ViewId::TextInput);
}
bool InfraredAppSceneLearnEnterName::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::Type::TextEditDone) {
auto remote_manager = app->get_remote_manager();
bool result = false;
if(app->get_learn_new_remote()) {
result = remote_manager->add_remote_with_button(
app->get_text_store(0), app->get_received_signal());
} else {
result =
remote_manager->add_button(app->get_text_store(0), app->get_received_signal());
}
if(!result) {
app->search_and_switch_to_previous_scene(
{InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList});
} else {
app->switch_to_next_scene_without_saving(InfraredApp::Scene::LearnDone);
}
}
return consumed;
}
void InfraredAppSceneLearnEnterName::on_exit(InfraredApp*) {
}

View file

@ -1,142 +0,0 @@
#include <gui/modules/dialog_ex.h>
#include <memory>
#include <dolphin/dolphin.h>
#include "../infrared_app.h"
#include "infrared.h"
static void dialog_result_callback(DialogExResult result, void* context) {
auto app = static_cast<InfraredApp*>(context);
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::DialogExSelected;
event.payload.dialog_ex_result = result;
app->get_view_manager()->send_event(&event);
}
void InfraredAppSceneLearnSuccess::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
DialogEx* dialog_ex = view_manager->get_dialog_ex();
DOLPHIN_DEED(DolphinDeedIrLearnSuccess);
app->notify_green_on();
infrared_worker_tx_set_get_signal_callback(
app->get_infrared_worker(), infrared_worker_tx_get_signal_steady_callback, app);
infrared_worker_tx_set_signal_sent_callback(
app->get_infrared_worker(), InfraredApp::signal_sent_callback, app);
auto signal = app->get_received_signal();
if(!signal.is_raw()) {
auto message = &signal.get_message();
uint8_t adr_digits =
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4);
uint8_t cmd_digits =
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4);
uint8_t max_digits = MAX(adr_digits, cmd_digits);
max_digits = MIN(max_digits, 7);
size_t label_x_offset = 63 + (7 - max_digits) * 3;
app->set_text_store(0, "%s", infrared_get_protocol_name(message->protocol));
app->set_text_store(
1,
"A: 0x%0*lX\nC: 0x%0*lX\n",
adr_digits,
message->address,
cmd_digits,
message->command);
dialog_ex_set_header(dialog_ex, app->get_text_store(0), 95, 7, AlignCenter, AlignCenter);
dialog_ex_set_text(
dialog_ex, app->get_text_store(1), label_x_offset, 34, AlignLeft, AlignCenter);
} else {
dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter);
app->set_text_store(0, "%d samples", signal.get_raw_signal().timings_cnt);
dialog_ex_set_text(dialog_ex, app->get_text_store(0), 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_DolphinReadingSuccess_59x63);
dialog_ex_set_result_callback(dialog_ex, dialog_result_callback);
dialog_ex_set_context(dialog_ex, app);
dialog_ex_enable_extended_events(dialog_ex);
view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx);
}
bool InfraredAppSceneLearnSuccess::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::Type::Tick) {
/* Send event every tick to suppress any switching off green light */
if(!button_pressed) {
app->notify_green_on();
}
}
if(event->type == InfraredAppEvent::Type::DialogExSelected) {
switch(event->payload.dialog_ex_result) {
case DialogExResultLeft:
consumed = true;
if(!button_pressed) {
app->switch_to_next_scene_without_saving(InfraredApp::Scene::Learn);
}
break;
case DialogExResultRight: {
consumed = true;
if(!button_pressed) {
app->switch_to_next_scene(InfraredApp::Scene::LearnEnterName);
}
break;
}
case DialogExPressCenter:
if(!button_pressed) {
button_pressed = true;
auto signal = app->get_received_signal();
if(signal.is_raw()) {
infrared_worker_set_raw_signal(
app->get_infrared_worker(),
signal.get_raw_signal().timings,
signal.get_raw_signal().timings_cnt);
} else {
infrared_worker_set_decoded_signal(
app->get_infrared_worker(), &signal.get_message());
}
infrared_worker_tx_start(app->get_infrared_worker());
}
break;
case DialogExReleaseCenter:
if(button_pressed) {
button_pressed = false;
infrared_worker_tx_stop(app->get_infrared_worker());
app->notify_green_off();
}
break;
default:
break;
}
}
if(event->type == InfraredAppEvent::Type::Back) {
if(!button_pressed) {
app->switch_to_next_scene(InfraredApp::Scene::AskBack);
}
consumed = true;
}
return consumed;
}
void InfraredAppSceneLearnSuccess::on_exit(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
DialogEx* dialog_ex = view_manager->get_dialog_ex();
dialog_ex_reset(dialog_ex);
app->notify_green_off();
infrared_worker_tx_set_get_signal_callback(app->get_infrared_worker(), nullptr, nullptr);
infrared_worker_tx_set_signal_sent_callback(app->get_infrared_worker(), nullptr, nullptr);
}

View file

@ -1,131 +0,0 @@
#include <gui/modules/button_menu.h>
#include <input/input.h>
#include <infrared_worker.h>
#include <dolphin/dolphin.h>
#include "../infrared_app.h"
#include "../infrared_app_view_manager.h"
typedef enum {
ButtonIndexPlus = -2,
ButtonIndexEdit = -1,
ButtonIndexNA = 0,
} ButtonIndex;
static void button_menu_callback(void* context, int32_t index, InputType type) {
InfraredApp* app = static_cast<InfraredApp*>(context);
InfraredAppEvent event;
if(type == InputTypePress) {
event.type = InfraredAppEvent::Type::MenuSelectedPress;
} else if(type == InputTypeRelease) {
event.type = InfraredAppEvent::Type::MenuSelectedRelease;
} else if(type == InputTypeShort) {
event.type = InfraredAppEvent::Type::MenuSelected;
} else {
furi_assert(0);
}
event.payload.menu_index = index;
app->get_view_manager()->send_event(&event);
}
void InfraredAppSceneRemote::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
ButtonMenu* button_menu = view_manager->get_button_menu();
auto remote_manager = app->get_remote_manager();
int i = 0;
button_pressed = false;
infrared_worker_tx_set_get_signal_callback(
app->get_infrared_worker(), infrared_worker_tx_get_signal_steady_callback, app);
infrared_worker_tx_set_signal_sent_callback(
app->get_infrared_worker(), InfraredApp::signal_sent_callback, app);
buttons_names = remote_manager->get_button_list();
i = 0;
for(auto& name : buttons_names) {
button_menu_add_item(
button_menu, name.c_str(), i++, button_menu_callback, ButtonMenuItemTypeCommon, app);
}
button_menu_add_item(
button_menu, "+", ButtonIndexPlus, button_menu_callback, ButtonMenuItemTypeControl, app);
button_menu_add_item(
button_menu, "Edit", ButtonIndexEdit, button_menu_callback, ButtonMenuItemTypeControl, app);
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(InfraredAppViewManager::ViewId::ButtonMenu);
}
bool InfraredAppSceneRemote::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = true;
if((event->type == InfraredAppEvent::Type::MenuSelected) ||
(event->type == InfraredAppEvent::Type::MenuSelectedPress) ||
(event->type == InfraredAppEvent::Type::MenuSelectedRelease)) {
switch(event->payload.menu_index) {
case ButtonIndexPlus:
furi_assert(event->type == InfraredAppEvent::Type::MenuSelected);
buttonmenu_item_selected = event->payload.menu_index;
app->set_learn_new_remote(false);
app->switch_to_next_scene(InfraredApp::Scene::Learn);
break;
case ButtonIndexEdit:
furi_assert(event->type == InfraredAppEvent::Type::MenuSelected);
buttonmenu_item_selected = event->payload.menu_index;
app->switch_to_next_scene(InfraredApp::Scene::Edit);
break;
default:
furi_assert(event->type != InfraredAppEvent::Type::MenuSelected);
bool pressed = (event->type == InfraredAppEvent::Type::MenuSelectedPress);
if(pressed && !button_pressed) {
button_pressed = true;
auto button_signal =
app->get_remote_manager()->get_button_data(event->payload.menu_index);
if(button_signal.is_raw()) {
infrared_worker_set_raw_signal(
app->get_infrared_worker(),
button_signal.get_raw_signal().timings,
button_signal.get_raw_signal().timings_cnt);
} else {
infrared_worker_set_decoded_signal(
app->get_infrared_worker(), &button_signal.get_message());
}
DOLPHIN_DEED(DolphinDeedIrSend);
infrared_worker_tx_start(app->get_infrared_worker());
} else if(!pressed && button_pressed) {
button_pressed = false;
infrared_worker_tx_stop(app->get_infrared_worker());
app->notify_green_off();
}
break;
}
} else if(event->type == InfraredAppEvent::Type::Back) {
if(!button_pressed) {
app->search_and_switch_to_previous_scene(
{InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList});
}
} else {
consumed = false;
}
return consumed;
}
void InfraredAppSceneRemote::on_exit(InfraredApp* app) {
infrared_worker_tx_set_get_signal_callback(app->get_infrared_worker(), nullptr, nullptr);
infrared_worker_tx_set_signal_sent_callback(app->get_infrared_worker(), nullptr, nullptr);
InfraredAppViewManager* view_manager = app->get_view_manager();
ButtonMenu* button_menu = view_manager->get_button_menu();
button_menu_reset(button_menu);
}

View file

@ -1,45 +0,0 @@
#include "../infrared_app.h"
#include "assets_icons.h"
#include "infrared/infrared_app_event.h"
#include <text_store.h>
void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) {
furi_assert(app);
bool result = false;
bool file_select_result;
auto remote_manager = app->get_remote_manager();
DialogsApp* dialogs = app->get_dialogs();
InfraredAppViewManager* view_manager = app->get_view_manager();
ButtonMenu* button_menu = view_manager->get_button_menu();
button_menu_reset(button_menu);
view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu);
file_select_result = dialog_file_browser_show(
dialogs,
app->file_path,
app->file_path,
InfraredApp::infrared_extension,
true,
&I_ir_10px,
true);
if(file_select_result) {
if(remote_manager->load(app->file_path)) {
app->switch_to_next_scene(InfraredApp::Scene::Remote);
result = true;
}
}
if(!result) {
app->switch_to_previous_scene();
}
}
bool InfraredAppSceneRemoteList::on_event(InfraredApp*, InfraredAppEvent*) {
return false;
}
void InfraredAppSceneRemoteList::on_exit(InfraredApp*) {
}

View file

@ -1,68 +0,0 @@
#include "../infrared_app.h"
typedef enum {
SubmenuIndexUniversalLibrary,
SubmenuIndexLearnNewRemote,
SubmenuIndexSavedRemotes,
} SubmenuIndex;
static void submenu_callback(void* context, uint32_t index) {
InfraredApp* app = static_cast<InfraredApp*>(context);
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::MenuSelected;
event.payload.menu_index = index;
app->get_view_manager()->send_event(&event);
}
void InfraredAppSceneStart::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Submenu* submenu = view_manager->get_submenu();
submenu_add_item(
submenu, "Universal Library", SubmenuIndexUniversalLibrary, submenu_callback, 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);
string_set_str(app->file_path, InfraredApp::infrared_directory);
submenu_item_selected = 0;
view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu);
}
bool InfraredAppSceneStart::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::Type::MenuSelected) {
submenu_item_selected = event->payload.menu_index;
switch(event->payload.menu_index) {
case SubmenuIndexUniversalLibrary:
app->switch_to_next_scene(InfraredApp::Scene::Universal);
break;
case SubmenuIndexLearnNewRemote:
app->set_learn_new_remote(true);
app->switch_to_next_scene(InfraredApp::Scene::Learn);
break;
case SubmenuIndexSavedRemotes:
app->switch_to_next_scene(InfraredApp::Scene::RemoteList);
break;
default:
furi_assert(0);
break;
}
consumed = true;
}
return consumed;
}
void InfraredAppSceneStart::on_exit(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Submenu* submenu = view_manager->get_submenu();
app->get_remote_manager()->reset_remote();
submenu_reset(submenu);
}

View file

@ -1,57 +0,0 @@
#include "../infrared_app.h"
typedef enum {
SubmenuIndexUniversalTV,
SubmenuIndexUniversalAudio,
SubmenuIndexUniversalAirConditioner,
} SubmenuIndex;
static void submenu_callback(void* context, uint32_t index) {
InfraredApp* app = static_cast<InfraredApp*>(context);
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::MenuSelected;
event.payload.menu_index = index;
app->get_view_manager()->send_event(&event);
}
void InfraredAppSceneUniversal::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Submenu* submenu = view_manager->get_submenu();
submenu_add_item(submenu, "TVs", SubmenuIndexUniversalTV, submenu_callback, app);
submenu_set_selected_item(submenu, submenu_item_selected);
submenu_item_selected = 0;
view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu);
}
bool InfraredAppSceneUniversal::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(event->type == InfraredAppEvent::Type::MenuSelected) {
submenu_item_selected = event->payload.menu_index;
switch(event->payload.menu_index) {
case SubmenuIndexUniversalTV:
app->switch_to_next_scene(InfraredApp::Scene::UniversalTV);
break;
case SubmenuIndexUniversalAudio:
// app->switch_to_next_scene(InfraredApp::Scene::UniversalAudio);
break;
case SubmenuIndexUniversalAirConditioner:
// app->switch_to_next_scene(InfraredApp::Scene::UniversalAirConditioner);
break;
}
consumed = true;
}
return consumed;
}
void InfraredAppSceneUniversal::on_exit(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
Submenu* submenu = view_manager->get_submenu();
submenu_reset(submenu);
}

View file

@ -1,107 +0,0 @@
#include <dolphin/dolphin.h>
#include <gui/modules/button_menu.h>
#include <gui/modules/button_panel.h>
#include <gui/view.h>
#include <gui/view_stack.h>
#include "../infrared_app.h"
#include "infrared/infrared_app_event.h"
#include "infrared/infrared_app_view_manager.h"
#include "infrared/scene/infrared_app_scene.h"
#include "../view/infrared_progress_view.h"
void InfraredAppSceneUniversalCommon::infrared_app_item_callback(void* context, uint32_t index) {
InfraredApp* app = static_cast<InfraredApp*>(context);
InfraredAppEvent event;
event.type = InfraredAppEvent::Type::ButtonPanelPressed;
event.payload.menu_index = index;
app->get_view_manager()->send_event(&event);
}
static void infrared_progress_back_callback(void* context) {
furi_assert(context);
auto app = static_cast<InfraredApp*>(context);
InfraredAppEvent infrared_event = {
.payload = {.dummy = 0},
.type = InfraredAppEvent::Type::Back,
};
app->get_view_manager()->clear_events();
app->get_view_manager()->send_event(&infrared_event);
}
void InfraredAppSceneUniversalCommon::hide_popup(InfraredApp* app) {
auto stack_view = app->get_view_manager()->get_universal_view_stack();
auto progress_view = app->get_view_manager()->get_progress();
view_stack_remove_view(stack_view, infrared_progress_view_get_view(progress_view));
}
void InfraredAppSceneUniversalCommon::show_popup(InfraredApp* app, int record_amount) {
auto stack_view = app->get_view_manager()->get_universal_view_stack();
auto progress_view = app->get_view_manager()->get_progress();
infrared_progress_view_set_progress_total(progress_view, record_amount);
infrared_progress_view_set_back_callback(progress_view, infrared_progress_back_callback, app);
view_stack_add_view(stack_view, infrared_progress_view_get_view(progress_view));
}
bool InfraredAppSceneUniversalCommon::progress_popup(InfraredApp* app) {
auto progress_view = app->get_view_manager()->get_progress();
return infrared_progress_view_increase_progress(progress_view);
}
bool InfraredAppSceneUniversalCommon::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
if(brute_force_started) {
if(event->type == InfraredAppEvent::Type::Tick) {
auto view_manager = app->get_view_manager();
app->notify_blink_send();
InfraredAppEvent tick_event = {
.payload = {.dummy = 0},
.type = InfraredAppEvent::Type::Tick,
};
view_manager->send_event(&tick_event);
bool result = brute_force.send_next_bruteforce();
if(result) {
result = progress_popup(app);
}
if(!result) {
brute_force.stop_bruteforce();
brute_force_started = false;
hide_popup(app);
}
consumed = true;
} else if(event->type == InfraredAppEvent::Type::Back) {
brute_force_started = false;
brute_force.stop_bruteforce();
hide_popup(app);
consumed = true;
}
} else {
if(event->type == InfraredAppEvent::Type::ButtonPanelPressed) {
int record_amount = 0;
if(brute_force.start_bruteforce(event->payload.menu_index, record_amount)) {
DOLPHIN_DEED(DolphinDeedIrBruteForce);
brute_force_started = true;
show_popup(app, record_amount);
app->notify_blink_send();
} else {
app->switch_to_previous_scene();
}
consumed = true;
} else if(event->type == InfraredAppEvent::Type::Back) {
app->switch_to_previous_scene();
consumed = true;
}
}
return consumed;
}
void InfraredAppSceneUniversalCommon::on_exit(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
ButtonPanel* button_panel = view_manager->get_button_panel();
button_panel_reset(button_panel);
}

View file

@ -1,123 +0,0 @@
#include <stdint.h>
#include <gui/modules/loading.h>
#include <gui/view_stack.h>
#include "infrared/scene/infrared_app_scene.h"
#include "infrared/infrared_app.h"
void InfraredAppSceneUniversalTV::on_enter(InfraredApp* app) {
InfraredAppViewManager* view_manager = app->get_view_manager();
ButtonPanel* button_panel = view_manager->get_button_panel();
button_panel_reserve(button_panel, 2, 3);
int i = 0;
button_panel_add_item(
button_panel,
i,
0,
0,
3,
19,
&I_Power_25x27,
&I_Power_hvr_25x27,
infrared_app_item_callback,
app);
brute_force.add_record(i, "POWER");
++i;
button_panel_add_item(
button_panel,
i,
1,
0,
36,
19,
&I_Mute_25x27,
&I_Mute_hvr_25x27,
infrared_app_item_callback,
app);
brute_force.add_record(i, "MUTE");
++i;
button_panel_add_item(
button_panel,
i,
0,
1,
3,
66,
&I_Vol_up_25x27,
&I_Vol_up_hvr_25x27,
infrared_app_item_callback,
app);
brute_force.add_record(i, "VOL+");
++i;
button_panel_add_item(
button_panel,
i,
1,
1,
36,
66,
&I_Up_25x27,
&I_Up_hvr_25x27,
infrared_app_item_callback,
app);
brute_force.add_record(i, "CH+");
++i;
button_panel_add_item(
button_panel,
i,
0,
2,
3,
98,
&I_Vol_down_25x27,
&I_Vol_down_hvr_25x27,
infrared_app_item_callback,
app);
brute_force.add_record(i, "VOL-");
++i;
button_panel_add_item(
button_panel,
i,
1,
2,
36,
98,
&I_Down_25x27,
&I_Down_hvr_25x27,
infrared_app_item_callback,
app);
brute_force.add_record(i, "CH-");
button_panel_add_label(button_panel, 6, 11, FontPrimary, "TV remote");
button_panel_add_label(button_panel, 9, 64, FontSecondary, "Vol");
button_panel_add_label(button_panel, 43, 64, FontSecondary, "Ch");
view_manager->switch_to(InfraredAppViewManager::ViewId::UniversalRemote);
auto stack_view = app->get_view_manager()->get_universal_view_stack();
auto loading_view = app->get_view_manager()->get_loading();
view_stack_add_view(stack_view, loading_get_view(loading_view));
/**
* Problem: Update events are not handled in Loading View, because:
* 1) Timer task has least prio
* 2) Storage service uses drivers that capture whole CPU time
* to handle SD communication
*
* Ugly workaround, but it works for current situation:
* raise timer task prio for DB scanning period.
*/
TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
TaskHandle_t storage_task = xTaskGetHandle("StorageSrv");
uint32_t timer_prio = uxTaskPriorityGet(timer_task);
uint32_t storage_prio = uxTaskPriorityGet(storage_task);
vTaskPrioritySet(timer_task, storage_prio + 1);
bool result = brute_force.calculate_messages();
vTaskPrioritySet(timer_task, timer_prio);
view_stack_remove_view(stack_view, loading_get_view(loading_view));
if(!result) {
app->switch_to_previous_scene();
}
}

View file

@ -0,0 +1,92 @@
#include "../../infrared_i.h"
#include <dolphin/dolphin.h>
void infrared_scene_universal_common_item_callback(void* context, uint32_t index) {
Infrared* infrared = context;
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index);
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
}
static void infrared_scene_universal_common_progress_back_callback(void* context) {
Infrared* infrared = context;
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1);
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
}
static void infrared_scene_universal_common_show_popup(Infrared* infrared, uint32_t record_count) {
ViewStack* view_stack = infrared->view_stack;
InfraredProgressView* progress = infrared->progress;
infrared_progress_view_set_progress_total(progress, record_count);
infrared_progress_view_set_back_callback(
progress, infrared_scene_universal_common_progress_back_callback, infrared);
view_stack_add_view(view_stack, infrared_progress_view_get_view(progress));
}
static void infrared_scene_universal_common_hide_popup(Infrared* infrared) {
ViewStack* view_stack = infrared->view_stack;
InfraredProgressView* progress = infrared->progress;
view_stack_remove_view(view_stack, infrared_progress_view_get_view(progress));
}
void infrared_scene_universal_common_on_enter(void* context) {
Infrared* infrared = context;
view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel));
}
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
InfraredBruteForce* brute_force = infrared->brute_force;
bool consumed = false;
if(infrared_brute_force_is_started(brute_force)) {
if(event.type == SceneManagerEventTypeTick) {
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkSend);
bool success = infrared_brute_force_send_next(brute_force);
if(success) {
success = infrared_progress_view_increase_progress(infrared->progress);
}
if(!success) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
}
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
consumed = true;
}
}
} else {
if(event.type == SceneManagerEventTypeBack) {
scene_manager_previous_scene(scene_manager);
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
if(infrared_custom_event_get_type(event.event) ==
InfraredCustomEventTypeButtonSelected) {
uint32_t record_count;
if(infrared_brute_force_start(
brute_force, infrared_custom_event_get_value(event.event), &record_count)) {
DOLPHIN_DEED(DolphinDeedIrBruteForce);
infrared_scene_universal_common_show_popup(infrared, record_count);
infrared_play_notification_message(
infrared, InfraredNotificationMessageBlinkSend);
} else {
scene_manager_previous_scene(scene_manager);
}
consumed = true;
}
}
}
return consumed;
}
void infrared_scene_universal_common_on_exit(void* context) {
Infrared* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
view_stack_remove_view(infrared->view_stack, button_panel_get_view(button_panel));
button_panel_reset(button_panel);
}

View file

@ -0,0 +1,8 @@
#pragma once
#include <gui/scene_manager.h>
void infrared_scene_universal_common_on_enter(void* context);
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event);
void infrared_scene_universal_common_on_exit(void* context);
void infrared_scene_universal_common_item_callback(void* context, uint32_t index);

View file

@ -0,0 +1,30 @@
#include "infrared_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const infrared_on_enter_handlers[])(void*) = {
#include "infrared_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_event handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const infrared_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "infrared_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_exit handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const infrared_on_exit_handlers[])(void* context) = {
#include "infrared_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers infrared_scene_handlers = {
.on_enter_handlers = infrared_on_enter_handlers,
.on_event_handlers = infrared_on_event_handlers,
.on_exit_handlers = infrared_on_exit_handlers,
.scene_num = InfraredSceneNum,
};

View file

@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) InfraredScene##id,
typedef enum {
#include "infrared_scene_config.h"
InfraredSceneNum,
} InfraredScene;
#undef ADD_SCENE
extern const SceneManagerHandlers infrared_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "infrared_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
#include "infrared_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
#include "infrared_scene_config.h"
#undef ADD_SCENE

View file

@ -0,0 +1,59 @@
#include "../infrared_i.h"
static void infrared_scene_dialog_result_callback(DialogExResult result, void* context) {
Infrared* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
}
void infrared_scene_ask_back_on_enter(void* context) {
Infrared* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex;
if(infrared->app_state.is_learning_new_remote) {
dialog_ex_set_header(dialog_ex, "Exit to Infrared menu?", 64, 0, AlignCenter, AlignTop);
} else {
dialog_ex_set_header(dialog_ex, "Exit to remote menu?", 64, 0, AlignCenter, AlignTop);
}
dialog_ex_set_text(
dialog_ex, "All unsaved data\nwill be lost", 64, 31, AlignCenter, AlignCenter);
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
dialog_ex_set_left_button_text(dialog_ex, "Exit");
dialog_ex_set_center_button_text(dialog_ex, NULL);
dialog_ex_set_right_button_text(dialog_ex, "Stay");
dialog_ex_set_result_callback(dialog_ex, infrared_scene_dialog_result_callback);
dialog_ex_set_context(dialog_ex, context);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
}
bool infrared_scene_ask_back_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeBack) {
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultLeft) {
if(infrared->app_state.is_learning_new_remote) {
scene_manager_search_and_switch_to_previous_scene(
scene_manager, InfraredSceneStart);
} else {
scene_manager_search_and_switch_to_previous_scene(
scene_manager, InfraredSceneRemote);
}
consumed = true;
} else if(event.event == DialogExResultRight) {
scene_manager_previous_scene(scene_manager);
consumed = true;
}
}
return consumed;
}
void infrared_scene_ask_back_on_exit(void* context) {
Infrared* infrared = context;
dialog_ex_reset(infrared->dialog_ex);
}

View file

@ -0,0 +1,17 @@
ADD_SCENE(infrared, start, Start)
ADD_SCENE(infrared, ask_back, AskBack)
ADD_SCENE(infrared, edit, Edit)
ADD_SCENE(infrared, edit_delete, EditDelete)
ADD_SCENE(infrared, edit_delete_done, EditDeleteDone)
ADD_SCENE(infrared, edit_button_select, EditButtonSelect)
ADD_SCENE(infrared, edit_rename, EditRename)
ADD_SCENE(infrared, edit_rename_done, EditRenameDone)
ADD_SCENE(infrared, learn, Learn)
ADD_SCENE(infrared, learn_done, LearnDone)
ADD_SCENE(infrared, learn_enter_name, LearnEnterName)
ADD_SCENE(infrared, learn_success, LearnSuccess)
ADD_SCENE(infrared, remote, Remote)
ADD_SCENE(infrared, remote_list, RemoteList)
ADD_SCENE(infrared, universal, Universal)
ADD_SCENE(infrared, universal_tv, UniversalTV)
ADD_SCENE(infrared, debug, Debug)

View file

@ -0,0 +1,68 @@
#include "../infrared_i.h"
void infrared_scene_debug_on_enter(void* context) {
Infrared* infrared = context;
InfraredWorker* worker = infrared->worker;
infrared_worker_rx_set_received_signal_callback(
worker, infrared_signal_received_callback, context);
infrared_worker_rx_enable_blink_on_receiving(worker, true);
infrared_worker_rx_start(worker);
infrared_debug_view_set_text(infrared->debug_view, "Received signals\nwill appear here");
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDebugView);
}
bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeSignalReceived) {
InfraredDebugView* debug_view = infrared->debug_view;
InfraredSignal* signal = infrared->received_signal;
if(infrared_signal_is_raw(signal)) {
InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
infrared_debug_view_set_text(debug_view, "RAW\n%d samples\n", raw->timings_size);
printf("RAW, %d samples:\r\n", raw->timings_size);
for(size_t i = 0; i < raw->timings_size; ++i) {
printf("%lu ", raw->timings[i]);
}
printf("\r\n");
} else {
InfraredMessage* message = infrared_signal_get_message(signal);
infrared_debug_view_set_text(
debug_view,
"%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n",
infrared_get_protocol_name(message->protocol),
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
message->address,
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
message->command,
message->repeat ? " R" : "");
printf(
"== %s, A:0x%0*lX, C:0x%0*lX%s ==\r\n",
infrared_get_protocol_name(message->protocol),
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
message->address,
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
message->command,
message->repeat ? " R" : "");
}
consumed = true;
}
}
return consumed;
}
void infrared_scene_debug_on_exit(void* context) {
Infrared* infrared = context;
InfraredWorker* worker = infrared->worker;
infrared_worker_rx_stop(worker);
infrared_worker_rx_enable_blink_on_receiving(worker, false);
}

View file

@ -0,0 +1,101 @@
#include "../infrared_i.h"
typedef enum {
SubmenuIndexAddButton,
SubmenuIndexRenameButton,
SubmenuIndexDeleteButton,
SubmenuIndexRenameRemote,
SubmenuIndexDeleteRemote,
} SubmenuIndex;
static void infrared_scene_edit_submenu_callback(void* context, uint32_t index) {
Infrared* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
}
void infrared_scene_edit_on_enter(void* context) {
Infrared* infrared = context;
Submenu* submenu = infrared->submenu;
SceneManager* scene_manager = infrared->scene_manager;
submenu_add_item(
submenu,
"Add Button",
SubmenuIndexAddButton,
infrared_scene_edit_submenu_callback,
context);
submenu_add_item(
submenu,
"Rename Button",
SubmenuIndexRenameButton,
infrared_scene_edit_submenu_callback,
context);
submenu_add_item(
submenu,
"Delete Button",
SubmenuIndexDeleteButton,
infrared_scene_edit_submenu_callback,
context);
submenu_add_item(
submenu,
"Rename Remote",
SubmenuIndexRenameRemote,
infrared_scene_edit_submenu_callback,
context);
submenu_add_item(
submenu,
"Delete Remote",
SubmenuIndexDeleteRemote,
infrared_scene_edit_submenu_callback,
context);
const uint32_t submenu_index = scene_manager_get_scene_state(scene_manager, InfraredSceneEdit);
submenu_set_selected_item(submenu, submenu_index);
scene_manager_set_scene_state(scene_manager, InfraredSceneEdit, SubmenuIndexAddButton);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
}
bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
const uint32_t submenu_index = event.event;
scene_manager_set_scene_state(scene_manager, InfraredSceneEdit, submenu_index);
if(submenu_index == SubmenuIndexAddButton) {
infrared->app_state.is_learning_new_remote = false;
scene_manager_next_scene(scene_manager, InfraredSceneLearn);
consumed = true;
} else if(submenu_index == SubmenuIndexRenameButton) {
infrared->app_state.edit_target = InfraredEditTargetButton;
infrared->app_state.edit_mode = InfraredEditModeRename;
scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect);
consumed = true;
} else if(submenu_index == SubmenuIndexDeleteButton) {
infrared->app_state.edit_target = InfraredEditTargetButton;
infrared->app_state.edit_mode = InfraredEditModeDelete;
scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect);
consumed = true;
} else if(submenu_index == SubmenuIndexRenameRemote) {
infrared->app_state.edit_target = InfraredEditTargetRemote;
infrared->app_state.edit_mode = InfraredEditModeRename;
scene_manager_next_scene(scene_manager, InfraredSceneEditRename);
consumed = true;
} else if(submenu_index == SubmenuIndexDeleteRemote) {
infrared->app_state.edit_target = InfraredEditTargetRemote;
infrared->app_state.edit_mode = InfraredEditModeDelete;
scene_manager_next_scene(scene_manager, InfraredSceneEditDelete);
consumed = true;
}
}
return consumed;
}
void infrared_scene_edit_on_exit(void* context) {
Infrared* infrared = context;
submenu_reset(infrared->submenu);
}

View file

@ -0,0 +1,63 @@
#include "../infrared_i.h"
static void infrared_scene_edit_button_select_submenu_callback(void* context, uint32_t index) {
Infrared* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
}
void infrared_scene_edit_button_select_on_enter(void* context) {
Infrared* infrared = context;
Submenu* submenu = infrared->submenu;
InfraredRemote* remote = infrared->remote;
InfraredAppState* app_state = &infrared->app_state;
const char* header = infrared->app_state.edit_mode == InfraredEditModeRename ?
"Rename Button:" :
"Delete Button:";
submenu_set_header(submenu, header);
const size_t button_count = infrared_remote_get_button_count(remote);
for(size_t i = 0; i < button_count; ++i) {
InfraredRemoteButton* button = infrared_remote_get_button(remote, i);
submenu_add_item(
submenu,
infrared_remote_button_get_name(button),
i,
infrared_scene_edit_button_select_submenu_callback,
context);
}
if(button_count && app_state->current_button_index != InfraredButtonIndexNone) {
submenu_set_selected_item(submenu, app_state->current_button_index);
app_state->current_button_index = InfraredButtonIndexNone;
}
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
}
bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredAppState* app_state = &infrared->app_state;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
app_state->current_button_index = event.event;
const InfraredEditMode edit_mode = app_state->edit_mode;
if(edit_mode == InfraredEditModeRename) {
scene_manager_next_scene(scene_manager, InfraredSceneEditRename);
} else if(edit_mode == InfraredEditModeDelete) {
scene_manager_next_scene(scene_manager, InfraredSceneEditDelete);
} else {
furi_assert(0);
}
consumed = true;
}
return consumed;
}
void infrared_scene_edit_button_select_on_exit(void* context) {
Infrared* infrared = context;
submenu_reset(infrared->submenu);
}

View file

@ -0,0 +1,112 @@
#include "../infrared_i.h"
static void
infrared_scene_edit_delete_dialog_result_callback(DialogExResult result, void* context) {
Infrared* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
}
void infrared_scene_edit_delete_on_enter(void* context) {
Infrared* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex;
InfraredRemote* remote = infrared->remote;
const InfraredEditTarget edit_target = infrared->app_state.edit_target;
if(edit_target == InfraredEditTargetButton) {
int32_t current_button_index = infrared->app_state.current_button_index;
furi_assert(current_button_index != InfraredButtonIndexNone);
dialog_ex_set_header(dialog_ex, "Delete button?", 64, 0, AlignCenter, AlignTop);
InfraredRemoteButton* current_button =
infrared_remote_get_button(remote, current_button_index);
InfraredSignal* signal = infrared_remote_button_get_signal(current_button);
if(infrared_signal_is_raw(signal)) {
const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
infrared_text_store_set(
infrared,
0,
"%s\nRAW\n%ld samples",
infrared_remote_button_get_name(current_button),
raw->timings_size);
} else {
const InfraredMessage* message = infrared_signal_get_message(signal);
infrared_text_store_set(
infrared,
0,
"%s\n%s\nA=0x%0*lX C=0x%0*lX",
infrared_remote_button_get_name(current_button),
infrared_get_protocol_name(message->protocol),
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
message->address,
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
message->command);
}
} else if(edit_target == InfraredEditTargetRemote) {
dialog_ex_set_header(dialog_ex, "Delete remote?", 64, 0, AlignCenter, AlignTop);
infrared_text_store_set(
infrared,
0,
"%s\n with %lu buttons",
infrared_remote_get_name(remote),
infrared_remote_get_button_count(remote));
} else {
furi_assert(0);
}
dialog_ex_set_text(dialog_ex, infrared->text_store[0], 64, 31, AlignCenter, AlignCenter);
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
dialog_ex_set_right_button_text(dialog_ex, "Delete");
dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_delete_dialog_result_callback);
dialog_ex_set_context(dialog_ex, context);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
}
bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultLeft) {
scene_manager_previous_scene(scene_manager);
consumed = true;
} else if(event.event == DialogExResultRight) {
bool success = false;
InfraredRemote* remote = infrared->remote;
InfraredAppState* app_state = &infrared->app_state;
const InfraredEditTarget edit_target = app_state->edit_target;
if(edit_target == InfraredEditTargetButton) {
furi_assert(app_state->current_button_index != InfraredButtonIndexNone);
success = infrared_remote_delete_button(remote, app_state->current_button_index);
app_state->current_button_index = InfraredButtonIndexNone;
} else if(edit_target == InfraredEditTargetRemote) {
success = infrared_remote_remove(remote);
app_state->current_button_index = InfraredButtonIndexNone;
} else {
furi_assert(0);
}
if(success) {
scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone);
} else {
const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
scene_manager_search_and_switch_to_previous_scene_one_of(
scene_manager, possible_scenes, sizeof(possible_scenes) / sizeof(uint32_t));
}
consumed = true;
}
}
return consumed;
}
void infrared_scene_edit_delete_on_exit(void* context) {
Infrared* infrared = context;
UNUSED(infrared);
}

View file

@ -0,0 +1,46 @@
#include "../infrared_i.h"
void infrared_scene_edit_delete_done_on_enter(void* context) {
Infrared* infrared = context;
Popup* popup = infrared->popup;
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
popup_set_callback(popup, infrared_popup_timeout_callback);
popup_set_context(popup, context);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
}
bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypePopupTimeout) {
const InfraredEditTarget edit_target = infrared->app_state.edit_target;
if(edit_target == InfraredEditTargetButton) {
scene_manager_search_and_switch_to_previous_scene(
scene_manager, InfraredSceneRemote);
} else if(edit_target == InfraredEditTargetRemote) {
const uint32_t possible_scenes[] = {InfraredSceneStart, InfraredSceneRemoteList};
scene_manager_search_and_switch_to_previous_scene_one_of(
scene_manager, possible_scenes, sizeof(possible_scenes) / sizeof(uint32_t));
} else {
furi_assert(0);
}
consumed = true;
}
}
return consumed;
}
void infrared_scene_edit_delete_done_on_exit(void* context) {
Infrared* infrared = context;
UNUSED(infrared);
}

View file

@ -0,0 +1,107 @@
#include "../infrared_i.h"
#include <string.h>
#include <toolbox/path.h>
void infrared_scene_edit_rename_on_enter(void* context) {
Infrared* infrared = context;
InfraredRemote* remote = infrared->remote;
TextInput* text_input = infrared->text_input;
size_t enter_name_length = 0;
const InfraredEditTarget edit_target = infrared->app_state.edit_target;
if(edit_target == InfraredEditTargetButton) {
text_input_set_header_text(text_input, "Name the button");
const int32_t current_button_index = infrared->app_state.current_button_index;
furi_assert(current_button_index != InfraredButtonIndexNone);
InfraredRemoteButton* current_button =
infrared_remote_get_button(remote, current_button_index);
enter_name_length = INFRARED_MAX_BUTTON_NAME_LENGTH;
strncpy(
infrared->text_store[0],
infrared_remote_button_get_name(current_button),
enter_name_length);
} else if(edit_target == InfraredEditTargetRemote) {
text_input_set_header_text(text_input, "Name the remote");
enter_name_length = INFRARED_MAX_REMOTE_NAME_LENGTH;
strncpy(infrared->text_store[0], infrared_remote_get_name(remote), enter_name_length);
string_t folder_path;
string_init(folder_path);
if(string_end_with_str_p(infrared->file_path, INFRARED_APP_EXTENSION)) {
path_extract_dirname(string_get_cstr(infrared->file_path), folder_path);
}
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
string_get_cstr(folder_path),
INFRARED_APP_EXTENSION,
infrared_remote_get_name(remote));
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
string_clear(folder_path);
} else {
furi_assert(0);
}
text_input_set_result_callback(
text_input,
infrared_text_input_callback,
context,
infrared->text_store[0],
enter_name_length,
false);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput);
}
bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredRemote* remote = infrared->remote;
SceneManager* scene_manager = infrared->scene_manager;
InfraredAppState* app_state = &infrared->app_state;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeTextEditDone) {
bool success = false;
const InfraredEditTarget edit_target = app_state->edit_target;
if(edit_target == InfraredEditTargetButton) {
const int32_t current_button_index = app_state->current_button_index;
furi_assert(current_button_index != InfraredButtonIndexNone);
success = infrared_remote_rename_button(
remote, infrared->text_store[0], current_button_index);
app_state->current_button_index = InfraredButtonIndexNone;
} else if(edit_target == InfraredEditTargetRemote) {
success = infrared_rename_current_remote(infrared, infrared->text_store[0]);
} else {
furi_assert(0);
}
if(success) {
scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone);
} else {
scene_manager_search_and_switch_to_previous_scene(
scene_manager, InfraredSceneRemoteList);
}
consumed = true;
}
}
return consumed;
}
void infrared_scene_edit_rename_on_exit(void* context) {
Infrared* infrared = context;
TextInput* text_input = infrared->text_input;
void* validator_context = text_input_get_validator_callback_context(text_input);
text_input_set_validator(text_input, NULL, NULL);
if(validator_context) {
validator_is_file_free((ValidatorIsFile*)validator_context);
}
}

View file

@ -0,0 +1,35 @@
#include "../infrared_i.h"
void infrared_scene_edit_rename_done_on_enter(void* context) {
Infrared* infrared = context;
Popup* popup = infrared->popup;
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
popup_set_callback(popup, infrared_popup_timeout_callback);
popup_set_context(popup, context);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
}
bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypePopupTimeout) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
consumed = true;
}
}
return consumed;
}
void infrared_scene_edit_rename_done_on_exit(void* context) {
Infrared* infrared = context;
UNUSED(infrared);
}

View file

@ -0,0 +1,46 @@
#include "../infrared_i.h"
void infrared_scene_learn_on_enter(void* context) {
Infrared* infrared = context;
Popup* popup = infrared->popup;
InfraredWorker* worker = infrared->worker;
infrared_worker_rx_set_received_signal_callback(
worker, infrared_signal_received_callback, context);
infrared_worker_rx_start(worker);
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);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
}
bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeTick) {
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkRead);
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeSignalReceived) {
infrared_worker_rx_set_received_signal_callback(infrared->worker, NULL, NULL);
infrared_play_notification_message(infrared, InfraredNotificationMessageSuccess);
scene_manager_next_scene(infrared->scene_manager, InfraredSceneLearnSuccess);
consumed = true;
}
}
return consumed;
}
void infrared_scene_learn_on_exit(void* context) {
Infrared* infrared = context;
Popup* popup = infrared->popup;
infrared_worker_rx_stop(infrared->worker);
popup_set_icon(popup, 0, 0, NULL);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignCenter);
}

View file

@ -0,0 +1,44 @@
#include "../infrared_i.h"
#include <dolphin/dolphin.h>
void infrared_scene_learn_done_on_enter(void* context) {
Infrared* infrared = context;
Popup* popup = infrared->popup;
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
DOLPHIN_DEED(DolphinDeedIrSave);
if(infrared->app_state.is_learning_new_remote) {
popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop);
} else {
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
}
popup_set_callback(popup, infrared_popup_timeout_callback);
popup_set_context(popup, context);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
}
bool infrared_scene_learn_done_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypePopupTimeout) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
consumed = true;
}
}
return consumed;
}
void infrared_scene_learn_done_on_exit(void* context) {
Infrared* infrared = context;
infrared->app_state.is_learning_new_remote = false;
popup_set_header(infrared->popup, NULL, 0, 0, AlignLeft, AlignTop);
}

View file

@ -0,0 +1,66 @@
#include "../infrared_i.h"
void infrared_scene_learn_enter_name_on_enter(void* context) {
Infrared* infrared = context;
TextInput* text_input = infrared->text_input;
InfraredSignal* signal = infrared->received_signal;
if(infrared_signal_is_raw(signal)) {
InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
infrared_text_store_set(infrared, 0, "RAW_%d", raw->timings_size);
} else {
InfraredMessage* message = infrared_signal_get_message(signal);
infrared_text_store_set(
infrared,
0,
"%.4s_%0*lX",
infrared_get_protocol_name(message->protocol),
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
message->command);
}
text_input_set_header_text(text_input, "Name the button");
text_input_set_result_callback(
text_input,
infrared_text_input_callback,
context,
infrared->text_store[0],
INFRARED_MAX_BUTTON_NAME_LENGTH,
true);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput);
}
bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredSignal* signal = infrared->received_signal;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeTextEditDone) {
bool success = false;
if(infrared->app_state.is_learning_new_remote) {
success =
infrared_add_remote_with_button(infrared, infrared->text_store[0], signal);
} else {
success =
infrared_remote_add_button(infrared->remote, infrared->text_store[0], signal);
}
if(success) {
scene_manager_next_scene(scene_manager, InfraredSceneLearnDone);
} else {
scene_manager_search_and_switch_to_previous_scene(
scene_manager, InfraredSceneRemoteList);
}
consumed = true;
}
}
return consumed;
}
void infrared_scene_learn_enter_name_on_exit(void* context) {
Infrared* infrared = context;
UNUSED(infrared);
}

View file

@ -0,0 +1,131 @@
#include "../infrared_i.h"
#include <dolphin/dolphin.h>
typedef enum {
InfraredSceneLearnSuccessStateIdle = 0,
InfraredSceneLearnSuccessStateSending = 1,
} InfraredSceneLearnSuccessState;
static void
infrared_scene_learn_success_dialog_result_callback(DialogExResult result, void* context) {
Infrared* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
}
void infrared_scene_learn_success_on_enter(void* context) {
Infrared* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex;
InfraredSignal* signal = infrared->received_signal;
DOLPHIN_DEED(DolphinDeedIrLearnSuccess);
infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn);
infrared_worker_tx_set_get_signal_callback(
infrared->worker, infrared_worker_tx_get_signal_steady_callback, context);
infrared_worker_tx_set_signal_sent_callback(
infrared->worker, infrared_signal_sent_callback, context);
if(infrared_signal_is_raw(signal)) {
InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter);
infrared_text_store_set(infrared, 0, "%d samples", raw->timings_size);
dialog_ex_set_text(dialog_ex, infrared->text_store[0], 75, 23, AlignLeft, AlignTop);
} else {
InfraredMessage* message = infrared_signal_get_message(signal);
uint8_t addr_digits =
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4);
uint8_t cmd_digits =
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4);
uint8_t max_digits = MAX(addr_digits, cmd_digits);
max_digits = MIN(max_digits, 7);
size_t label_x_offset = 63 + (7 - max_digits) * 3;
infrared_text_store_set(infrared, 0, "%s", infrared_get_protocol_name(message->protocol));
infrared_text_store_set(
infrared,
1,
"A: 0x%0*lX\nC: 0x%0*lX\n",
addr_digits,
message->address,
cmd_digits,
message->command);
dialog_ex_set_header(dialog_ex, infrared->text_store[0], 95, 7, AlignCenter, AlignCenter);
dialog_ex_set_text(
dialog_ex, infrared->text_store[1], label_x_offset, 34, AlignLeft, AlignCenter);
}
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_DolphinReadingSuccess_59x63);
dialog_ex_set_result_callback(dialog_ex, infrared_scene_learn_success_dialog_result_callback);
dialog_ex_set_context(dialog_ex, context);
dialog_ex_enable_extended_events(dialog_ex);
scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneLearnSuccess, InfraredSceneLearnSuccessStateIdle);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
}
bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
uint32_t scene_state = scene_manager_get_scene_state(scene_manager, InfraredSceneLearnSuccess);
bool consumed = false;
if(event.type == SceneManagerEventTypeTick) {
if(scene_state == InfraredSceneLearnSuccessStateIdle) {
infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn);
}
consumed = true;
} else if(event.type == SceneManagerEventTypeBack) {
if(scene_state == InfraredSceneLearnSuccessStateIdle) {
scene_manager_next_scene(scene_manager, InfraredSceneAskBack);
}
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultLeft) {
if(scene_state == InfraredSceneLearnSuccessStateIdle) {
scene_manager_search_and_switch_to_previous_scene(
scene_manager, InfraredSceneLearn);
}
consumed = true;
} else if(event.event == DialogExResultRight) {
if(scene_state == InfraredSceneLearnSuccessStateIdle) {
scene_manager_next_scene(scene_manager, InfraredSceneLearnEnterName);
}
consumed = true;
} else if(event.event == DialogExPressCenter) {
if(scene_state == InfraredSceneLearnSuccessStateIdle) {
scene_manager_set_scene_state(
scene_manager,
InfraredSceneLearnSuccess,
InfraredSceneLearnSuccessStateSending);
infrared_tx_start_received(infrared);
}
consumed = true;
} else if(event.event == DialogExReleaseCenter) {
if(scene_state == InfraredSceneLearnSuccessStateSending) {
scene_manager_set_scene_state(
scene_manager, InfraredSceneLearnSuccess, InfraredSceneLearnSuccessStateIdle);
infrared_tx_stop(infrared);
infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff);
}
consumed = true;
}
}
return consumed;
}
void infrared_scene_learn_success_on_exit(void* context) {
Infrared* infrared = context;
InfraredWorker* worker = infrared->worker;
dialog_ex_reset(infrared->dialog_ex);
infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff);
infrared_worker_tx_set_get_signal_callback(worker, NULL, NULL);
infrared_worker_tx_set_signal_sent_callback(worker, NULL, NULL);
}

View file

@ -0,0 +1,119 @@
#include "../infrared_i.h"
typedef enum {
ButtonIndexPlus = -2,
ButtonIndexEdit = -1,
ButtonIndexNA = 0,
} ButtonIndex;
static void
infrared_scene_remote_button_menu_callback(void* context, int32_t index, InputType type) {
Infrared* infrared = context;
uint16_t custom_type;
if(type == InputTypePress) {
custom_type = InfraredCustomEventTypeTransmitStarted;
} else if(type == InputTypeRelease) {
custom_type = InfraredCustomEventTypeTransmitStopped;
} else if(type == InputTypeShort) {
custom_type = InfraredCustomEventTypeMenuSelected;
} else {
furi_crash("Unexpected input type");
}
view_dispatcher_send_custom_event(
infrared->view_dispatcher, infrared_custom_event_pack(custom_type, index));
}
void infrared_scene_remote_on_enter(void* context) {
Infrared* infrared = context;
InfraredRemote* remote = infrared->remote;
ButtonMenu* button_menu = infrared->button_menu;
SceneManager* scene_manager = infrared->scene_manager;
infrared_worker_tx_set_get_signal_callback(
infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared);
infrared_worker_tx_set_signal_sent_callback(
infrared->worker, infrared_signal_sent_callback, infrared);
size_t button_count = infrared_remote_get_button_count(remote);
for(size_t i = 0; i < button_count; ++i) {
InfraredRemoteButton* button = infrared_remote_get_button(remote, i);
button_menu_add_item(
button_menu,
infrared_remote_button_get_name(button),
i,
infrared_scene_remote_button_menu_callback,
ButtonMenuItemTypeCommon,
context);
}
button_menu_add_item(
button_menu,
"+",
ButtonIndexPlus,
infrared_scene_remote_button_menu_callback,
ButtonMenuItemTypeControl,
context);
button_menu_add_item(
button_menu,
"Edit",
ButtonIndexEdit,
infrared_scene_remote_button_menu_callback,
ButtonMenuItemTypeControl,
context);
button_menu_set_header(button_menu, infrared_remote_get_name(remote));
const int16_t button_index =
(signed)scene_manager_get_scene_state(scene_manager, InfraredSceneRemote);
button_menu_set_selected_item(button_menu, button_index);
scene_manager_set_scene_state(scene_manager, InfraredSceneRemote, ButtonIndexNA);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewButtonMenu);
}
bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeBack) {
const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
scene_manager_search_and_switch_to_previous_scene_one_of(
scene_manager, possible_scenes, sizeof(possible_scenes) / sizeof(uint32_t));
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
const uint16_t custom_type = infrared_custom_event_get_type(event.event);
const int16_t button_index = infrared_custom_event_get_value(event.event);
if(custom_type == InfraredCustomEventTypeTransmitStarted) {
furi_assert(button_index >= 0);
infrared_tx_start_button_index(infrared, button_index);
consumed = true;
} else if(custom_type == InfraredCustomEventTypeTransmitStopped) {
infrared_tx_stop(infrared);
consumed = true;
} else if(custom_type == InfraredCustomEventTypeMenuSelected) {
furi_assert(button_index < 0);
scene_manager_set_scene_state(
scene_manager, InfraredSceneRemote, (unsigned)button_index);
if(button_index == ButtonIndexPlus) {
infrared->app_state.is_learning_new_remote = false;
scene_manager_next_scene(scene_manager, InfraredSceneLearn);
consumed = true;
} else if(button_index == ButtonIndexEdit) {
scene_manager_next_scene(scene_manager, InfraredSceneEdit);
consumed = true;
}
}
}
return consumed;
}
void infrared_scene_remote_on_exit(void* context) {
Infrared* infrared = context;
infrared_worker_tx_set_get_signal_callback(infrared->worker, NULL, NULL);
infrared_worker_tx_set_signal_sent_callback(infrared->worker, NULL, NULL);
button_menu_reset(infrared->button_menu);
}

View file

@ -0,0 +1,44 @@
#include "../infrared_i.h"
void infrared_scene_remote_list_on_enter(void* context) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
string_set_str(infrared->file_path, INFRARED_APP_FOLDER);
bool success = dialog_file_browser_show(
infrared->dialogs,
infrared->file_path,
infrared->file_path,
INFRARED_APP_EXTENSION,
true,
&I_ir_10px,
true);
if(success) {
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal);
view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
success = infrared_remote_load(infrared->remote, infrared->file_path);
infrared_show_loading_popup(infrared, false);
}
if(success) {
scene_manager_next_scene(scene_manager, InfraredSceneRemote);
} else {
scene_manager_previous_scene(scene_manager);
}
}
bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
bool consumed = false;
return consumed;
}
void infrared_scene_remote_list_on_exit(void* context) {
UNUSED(context);
}

View file

@ -0,0 +1,83 @@
#include "../infrared_i.h"
enum SubmenuIndex {
SubmenuIndexUniversalRemotes,
SubmenuIndexLearnNewRemote,
SubmenuIndexSavedRemotes,
SubmenuIndexDebug
};
static void infrared_scene_start_submenu_callback(void* context, uint32_t index) {
Infrared* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
}
void infrared_scene_start_on_enter(void* context) {
Infrared* infrared = context;
Submenu* submenu = infrared->submenu;
SceneManager* scene_manager = infrared->scene_manager;
submenu_add_item(
submenu,
"Universal Remotes",
SubmenuIndexUniversalRemotes,
infrared_scene_start_submenu_callback,
infrared);
submenu_add_item(
submenu,
"Learn New Remote",
SubmenuIndexLearnNewRemote,
infrared_scene_start_submenu_callback,
infrared);
submenu_add_item(
submenu,
"Saved Remotes",
SubmenuIndexSavedRemotes,
infrared_scene_start_submenu_callback,
infrared);
if(infrared->app_state.is_debug_enabled) {
submenu_add_item(
submenu, "Debug", SubmenuIndexDebug, infrared_scene_start_submenu_callback, infrared);
}
const uint32_t submenu_index =
scene_manager_get_scene_state(scene_manager, InfraredSceneStart);
submenu_set_selected_item(submenu, submenu_index);
scene_manager_set_scene_state(scene_manager, InfraredSceneStart, SubmenuIndexUniversalRemotes);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
}
bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
const uint32_t submenu_index = event.event;
scene_manager_set_scene_state(scene_manager, InfraredSceneStart, submenu_index);
if(submenu_index == SubmenuIndexUniversalRemotes) {
scene_manager_next_scene(scene_manager, InfraredSceneUniversal);
consumed = true;
} else if(submenu_index == SubmenuIndexLearnNewRemote) {
infrared->app_state.is_learning_new_remote = true;
scene_manager_next_scene(scene_manager, InfraredSceneLearn);
consumed = true;
} else if(submenu_index == SubmenuIndexSavedRemotes) {
scene_manager_next_scene(scene_manager, InfraredSceneRemoteList);
consumed = true;
} else if(submenu_index == SubmenuIndexDebug) {
scene_manager_next_scene(scene_manager, InfraredSceneDebug);
consumed = true;
}
}
return consumed;
}
void infrared_scene_start_on_exit(void* context) {
Infrared* infrared = context;
submenu_reset(infrared->submenu);
}

View file

@ -0,0 +1,53 @@
#include "../infrared_i.h"
typedef enum {
SubmenuIndexUniversalTV,
SubmenuIndexUniversalAudio,
SubmenuIndexUniversalAirConditioner,
} SubmenuIndex;
static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) {
Infrared* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
}
void infrared_scene_universal_on_enter(void* context) {
Infrared* infrared = context;
Submenu* submenu = infrared->submenu;
submenu_add_item(
submenu,
"TVs",
SubmenuIndexUniversalTV,
infrared_scene_universal_submenu_callback,
context);
submenu_set_selected_item(submenu, 0);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
}
bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexUniversalTV) {
scene_manager_next_scene(scene_manager, InfraredSceneUniversalTV);
consumed = true;
} else if(event.event == SubmenuIndexUniversalAudio) {
//TODO Implement Audio universal remote
consumed = true;
} else if(event.event == SubmenuIndexUniversalAirConditioner) {
//TODO Implement A/C universal remote
consumed = true;
}
}
return consumed;
}
void infrared_scene_universal_on_exit(void* context) {
Infrared* infrared = context;
submenu_reset(infrared->submenu);
}

View file

@ -0,0 +1,111 @@
#include "../infrared_i.h"
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_tv_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
Infrared* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;
infrared_brute_force_set_db_filename(brute_force, "/ext/infrared/assets/tv.ir");
button_panel_reserve(button_panel, 2, 3);
uint32_t i = 0;
button_panel_add_item(
button_panel,
i,
0,
0,
3,
19,
&I_Power_25x27,
&I_Power_hvr_25x27,
infrared_scene_universal_common_item_callback,
context);
infrared_brute_force_add_record(brute_force, i++, "POWER");
button_panel_add_item(
button_panel,
i,
1,
0,
36,
19,
&I_Mute_25x27,
&I_Mute_hvr_25x27,
infrared_scene_universal_common_item_callback,
context);
infrared_brute_force_add_record(brute_force, i++, "MUTE");
button_panel_add_item(
button_panel,
i,
0,
1,
3,
66,
&I_Vol_up_25x27,
&I_Vol_up_hvr_25x27,
infrared_scene_universal_common_item_callback,
context);
infrared_brute_force_add_record(brute_force, i++, "VOL+");
button_panel_add_item(
button_panel,
i,
1,
1,
36,
66,
&I_Up_25x27,
&I_Up_hvr_25x27,
infrared_scene_universal_common_item_callback,
context);
infrared_brute_force_add_record(brute_force, i++, "CH+");
button_panel_add_item(
button_panel,
i,
0,
2,
3,
98,
&I_Vol_down_25x27,
&I_Vol_down_hvr_25x27,
infrared_scene_universal_common_item_callback,
context);
infrared_brute_force_add_record(brute_force, i++, "VOL-");
button_panel_add_item(
button_panel,
i,
1,
2,
36,
98,
&I_Down_25x27,
&I_Down_hvr_25x27,
infrared_scene_universal_common_item_callback,
context);
infrared_brute_force_add_record(brute_force, i++, "CH-");
button_panel_add_label(button_panel, 6, 11, FontPrimary, "TV remote");
button_panel_add_label(button_panel, 9, 64, FontSecondary, "Vol");
button_panel_add_label(button_panel, 43, 64, FontSecondary, "Ch");
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_previous_scene(infrared->scene_manager);
}
}
bool infrared_scene_universal_tv_on_event(void* context, SceneManagerEvent event) {
return infrared_scene_universal_common_on_event(context, event);
}
void infrared_scene_universal_tv_on_exit(void* context) {
infrared_scene_universal_common_on_exit(context);
}

View file

@ -0,0 +1,59 @@
#include "infrared_debug_view.h"
#include <stdlib.h>
#include <string.h>
#include <gui/canvas.h>
#include <gui/elements.h>
#define INFRARED_DEBUG_TEXT_LENGTH 64
struct InfraredDebugView {
View* view;
};
typedef struct {
char text[INFRARED_DEBUG_TEXT_LENGTH];
} InfraredDebugViewModel;
static void infrared_debug_view_draw_callback(Canvas* canvas, void* model) {
InfraredDebugViewModel* debug_view_model = model;
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 64, 0, AlignCenter, AlignTop, "INFRARED monitor\n");
canvas_set_font(canvas, FontKeyboard);
if(strlen(debug_view_model->text)) {
elements_multiline_text_aligned(
canvas, 64, 43, AlignCenter, AlignCenter, debug_view_model->text);
}
}
InfraredDebugView* infrared_debug_view_alloc() {
InfraredDebugView* debug_view = malloc(sizeof(InfraredDebugView));
debug_view->view = view_alloc();
view_allocate_model(debug_view->view, ViewModelTypeLocking, sizeof(InfraredDebugViewModel));
view_set_draw_callback(debug_view->view, infrared_debug_view_draw_callback);
view_set_context(debug_view->view, debug_view);
return debug_view;
}
void infrared_debug_view_free(InfraredDebugView* debug_view) {
view_free(debug_view->view);
free(debug_view);
}
View* infrared_debug_view_get_view(InfraredDebugView* debug_view) {
return debug_view->view;
}
void infrared_debug_view_set_text(InfraredDebugView* debug_view, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
InfraredDebugViewModel* model = view_get_model(debug_view->view);
vsnprintf(model->text, INFRARED_DEBUG_TEXT_LENGTH, fmt, args);
view_commit_model(debug_view->view, true);
va_end(args);
}

View file

@ -0,0 +1,11 @@
#pragma once
#include <gui/view.h>
typedef struct InfraredDebugView InfraredDebugView;
InfraredDebugView* infrared_debug_view_alloc();
void infrared_debug_view_free(InfraredDebugView* debug_view);
View* infrared_debug_view_get_view(InfraredDebugView* debug_view);
void infrared_debug_view_set_text(InfraredDebugView* debug_view, const char* fmt, ...);

View file

@ -1,140 +0,0 @@
#include <gui/canvas.h>
#include <input/input.h>
#include <infrared.h>
#include <infrared_worker.h>
#include <stdio.h>
#include <furi.h>
#include <furi_hal_infrared.h>
#include <furi_hal.h>
#include <gui/view_port.h>
#include <gui/gui.h>
#include <gui/elements.h>
#define INFRARED_TIMINGS_SIZE 700
typedef struct {
uint32_t timing_cnt;
struct {
uint8_t level;
uint32_t duration;
} timing[INFRARED_TIMINGS_SIZE];
} InfraredDelaysArray;
typedef struct {
char display_text[64];
osMessageQueueId_t event_queue;
InfraredDelaysArray delays;
InfraredWorker* worker;
ViewPort* view_port;
} InfraredMonitor;
void infrared_monitor_input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
InfraredMonitor* infrared_monitor = (InfraredMonitor*)ctx;
if((input_event->type == InputTypeShort) && (input_event->key == InputKeyBack)) {
osMessageQueuePut(infrared_monitor->event_queue, input_event, 0, 0);
}
}
static void infrared_monitor_draw_callback(Canvas* canvas, void* ctx) {
furi_assert(canvas);
furi_assert(ctx);
InfraredMonitor* infrared_monitor = (InfraredMonitor*)ctx;
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 64, 0, AlignCenter, AlignTop, "INFRARED monitor\n");
canvas_set_font(canvas, FontKeyboard);
if(strlen(infrared_monitor->display_text)) {
elements_multiline_text_aligned(
canvas, 64, 43, AlignCenter, AlignCenter, infrared_monitor->display_text);
}
}
static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
furi_assert(context);
furi_assert(received_signal);
InfraredMonitor* infrared_monitor = context;
if(infrared_worker_signal_is_decoded(received_signal)) {
const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal);
snprintf(
infrared_monitor->display_text,
sizeof(infrared_monitor->display_text),
"%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n",
infrared_get_protocol_name(message->protocol),
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
message->address,
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
message->command,
message->repeat ? " R" : "");
view_port_update(infrared_monitor->view_port);
printf(
"== %s, A:0x%0*lX, C:0x%0*lX%s ==\r\n",
infrared_get_protocol_name(message->protocol),
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
message->address,
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
message->command,
message->repeat ? " R" : "");
} else {
const uint32_t* timings;
size_t timings_cnt;
infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt);
snprintf(
infrared_monitor->display_text,
sizeof(infrared_monitor->display_text),
"RAW\n%d samples\n",
timings_cnt);
view_port_update(infrared_monitor->view_port);
printf("RAW, %d samples:\r\n", timings_cnt);
for(size_t i = 0; i < timings_cnt; ++i) {
printf("%lu ", timings[i]);
}
printf("\r\n");
}
}
int32_t infrared_monitor_app(void* p) {
(void)p;
InfraredMonitor* infrared_monitor = malloc(sizeof(InfraredMonitor));
infrared_monitor->display_text[0] = 0;
infrared_monitor->event_queue = osMessageQueueNew(1, sizeof(InputEvent), NULL);
infrared_monitor->view_port = view_port_alloc();
Gui* gui = furi_record_open("gui");
view_port_draw_callback_set(
infrared_monitor->view_port, infrared_monitor_draw_callback, infrared_monitor);
view_port_input_callback_set(
infrared_monitor->view_port, infrared_monitor_input_callback, infrared_monitor);
gui_add_view_port(gui, infrared_monitor->view_port, GuiLayerFullscreen);
infrared_monitor->worker = infrared_worker_alloc();
infrared_worker_rx_start(infrared_monitor->worker);
infrared_worker_rx_set_received_signal_callback(
infrared_monitor->worker, signal_received_callback, infrared_monitor);
infrared_worker_rx_enable_blink_on_receiving(infrared_monitor->worker, true);
while(1) {
InputEvent event;
if(osOK == osMessageQueueGet(infrared_monitor->event_queue, &event, NULL, 50)) {
if((event.type == InputTypeShort) && (event.key == InputKeyBack)) {
break;
}
}
}
infrared_worker_rx_stop(infrared_monitor->worker);
infrared_worker_free(infrared_monitor->worker);
osMessageQueueDelete(infrared_monitor->event_queue);
view_port_enabled_set(infrared_monitor->view_port, false);
gui_remove_view_port(gui, infrared_monitor->view_port);
view_port_free(infrared_monitor->view_port);
furi_record_close("gui");
free(infrared_monitor);
return 0;
}