[FL-2279] IR doxygen, rename irda -> infrared (#1010)

* IR: Doxygen docs, some rename
* Rename irda -> infrared
* Rollback collateral renames

Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
Albert Kharisov
2022-02-25 19:22:58 +04:00
committed by GitHub
parent c42cce3c6c
commit 052237f8c9
159 changed files with 6387 additions and 5622 deletions

View File

@@ -0,0 +1,196 @@
#include <furi_hal_delay.h>
#include <infrared.h>
#include <app_template.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"
static void infrared_cli_start_ir_rx(Cli* cli, string_t args);
static void infrared_cli_start_ir_tx(Cli* cli, string_t args);
static const struct {
const char* cmd;
void (*process_function)(Cli* cli, string_t args);
} infrared_cli_commands[] = {
{.cmd = "rx", .process_function = infrared_cli_start_ir_rx},
{.cmd = "tx", .process_function = infrared_cli_start_ir_tx},
};
static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
furi_assert(received_signal);
char buf[100];
size_t buf_cnt;
Cli* cli = (Cli*)context;
if(infrared_worker_signal_is_decoded(received_signal)) {
const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal);
buf_cnt = sniprintf(
buf,
sizeof(buf),
"%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" : "");
cli_write(cli, (uint8_t*)buf, buf_cnt);
} else {
const uint32_t* timings;
size_t timings_cnt;
infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt);
buf_cnt = sniprintf(buf, sizeof(buf), "RAW, %d samples:\r\n", timings_cnt);
cli_write(cli, (uint8_t*)buf, buf_cnt);
for(size_t i = 0; i < timings_cnt; ++i) {
buf_cnt = sniprintf(buf, sizeof(buf), "%lu ", timings[i]);
cli_write(cli, (uint8_t*)buf, buf_cnt);
}
buf_cnt = sniprintf(buf, sizeof(buf), "\r\n");
cli_write(cli, (uint8_t*)buf, buf_cnt);
}
}
static void infrared_cli_start_ir_rx(Cli* cli, string_t args) {
InfraredWorker* worker = infrared_worker_alloc();
infrared_worker_rx_start(worker);
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli);
printf("Receiving INFRARED...\r\nPress Ctrl+C to abort\r\n");
while(!cli_cmd_interrupt_received(cli)) {
delay(50);
}
infrared_worker_rx_stop(worker);
infrared_worker_free(worker);
}
static void infrared_cli_print_usage(void) {
printf("Usage:\r\n");
printf("\tir rx\r\n");
printf("\tir tx <protocol> <address> <command>\r\n");
printf("\t<command> and <address> are hex-formatted\r\n");
printf("\tAvailable protocols:");
for(int i = 0; infrared_is_protocol_valid((InfraredProtocol)i); ++i) {
printf(" %s", infrared_get_protocol_name((InfraredProtocol)i));
}
printf("\r\n");
printf("\tRaw format:\r\n");
printf("\tir_tx RAW F:<frequency> DC:<duty_cycle> <sample0> <sample1>...\r\n");
printf(
"\tFrequency (%d - %d), Duty cycle (0 - 100), max 512 samples\r\n",
INFRARED_MIN_FREQUENCY,
INFRARED_MAX_FREQUENCY);
}
static bool parse_message(const char* str, InfraredMessage* message) {
char protocol_name[32];
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);
}
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];
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;
uint32_t timings_cnt_max = *timings_cnt;
*timings_cnt = 0;
while(1) {
char timing_str[10];
for(; *str == ' '; ++str)
;
if(1 != sscanf(str, "%9s", timing_str)) 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;
}
return infrared_parser_is_raw_signal_valid(*frequency, *duty_cycle, *timings_cnt);
}
static void infrared_cli_start_ir_tx(Cli* cli, string_t args) {
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);
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);
} else {
printf("Wrong arguments.\r\n");
infrared_cli_print_usage();
}
free(timings);
}
static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) {
if(furi_hal_infrared_is_busy()) {
printf("INFRARED is busy. Exit.");
return;
}
size_t i = 0;
for(; i < COUNT_OF(infrared_cli_commands); ++i) {
size_t size = strlen(infrared_cli_commands[i].cmd);
bool cmd_found = !strncmp(string_get_cstr(args), infrared_cli_commands[i].cmd, size);
if(cmd_found) {
if(string_size(args) == size) {
break;
}
if(string_get_cstr(args)[size] == ' ') {
string_right(args, size);
break;
}
}
}
if(i < COUNT_OF(infrared_cli_commands)) {
infrared_cli_commands[i].process_function(cli, args);
} else {
infrared_cli_print_usage();
}
}
extern "C" 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);
furi_record_close("cli");
#endif
}

View File

@@ -0,0 +1,157 @@
#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", 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

@@ -0,0 +1,48 @@
/**
* @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,273 @@
#include "infrared_app.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) {
std::string full_name = static_cast<const char*>(args);
std::string remote_name(full_name, full_name.find_last_of('/') + 1, full_name.size());
remote_name.erase(remote_name.find_last_of('.'));
bool result = remote_manager.load(remote_name);
if(result) {
current_scene = InfraredApp::Scene::Remote;
} else {
printf("Failed to load remote \'%s\'\r\n", remote_name.c_str());
return -1;
}
}
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());
notification = static_cast<NotificationApp*>(furi_record_open("notification"));
infrared_worker = infrared_worker_alloc();
}
InfraredApp::~InfraredApp() {
infrared_worker_free(infrared_worker);
furi_record_close("notification");
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_red_blink() {
notification_message(notification, &sequence_blink_red_10);
}
void InfraredApp::notify_click() {
static const NotificationSequence sequence = {
&message_click,
&message_delay_1,
&message_sound_off,
NULL,
};
notification_message_block(notification, &sequence);
}
void InfraredApp::notify_click_and_green_blink() {
static const NotificationSequence sequence = {
&message_click,
&message_delay_1,
&message_sound_off,
&message_green_255,
&message_delay_10,
&message_green_0,
&message_do_not_reset,
NULL,
};
notification_message_block(notification, &sequence);
}
void InfraredApp::notify_blink_green() {
static const NotificationSequence sequence = {
&message_green_255,
&message_delay_10,
&message_green_0,
&message_do_not_reset,
NULL,
};
notification_message(notification, &sequence);
}
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_green();
}

View File

@@ -0,0 +1,322 @@
/**
* @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 <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_red_blink();
/** Light green */
void notify_green_on();
/** Disable green light */
void notify_green_off();
/** Play click sound */
void notify_click();
/** Play click and green notification */
void notify_click_and_green_blink();
/** Blink green light */
void notify_blink_green();
/** 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();
/** 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;
/** 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

@@ -0,0 +1,94 @@
#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>
#include <file_worker_cpp.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

@@ -0,0 +1,67 @@
/**
* @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

@@ -0,0 +1,47 @@
/**
* @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 {
/** 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

@@ -0,0 +1,211 @@
#include <file_worker_cpp.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"
static const std::string default_remote_name = "remote";
std::string InfraredAppRemoteManager::make_full_name(const std::string& remote_name) const {
return std::string("") + InfraredApp::infrared_directory + "/" + remote_name +
InfraredApp::infrared_extension;
}
std::string InfraredAppRemoteManager::find_vacant_remote_name(const std::string& name) {
bool exist = true;
FileWorkerCpp file_worker;
if(!file_worker.is_file_exist(make_full_name(name).c_str(), &exist)) {
return std::string();
} else if(!exist) {
return name;
}
/* if suggested name is occupied, try another one (name2, name3, etc) */
uint32_t i = 1;
bool file_worker_result = false;
std::string new_name;
do {
new_name = make_full_name(name + std::to_string(++i));
file_worker_result = file_worker.is_file_exist(new_name.c_str(), &exist);
} while(file_worker_result && exist);
return !exist ? name + std::to_string(i) : std::string();
}
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);
auto new_name = find_vacant_remote_name(default_remote_name);
if(new_name.empty()) {
return false;
}
remote = std::make_unique<InfraredAppRemote>(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() {
bool result;
FileWorkerCpp file_worker;
result = file_worker.remove(make_full_name(remote->name).c_str());
reset_remote();
return result;
}
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);
if(!remote->name.compare(str)) {
return true;
}
auto new_name = find_vacant_remote_name(str);
if(new_name.empty()) {
return false;
}
FileWorkerCpp file_worker;
std::string old_filename = make_full_name(remote->name);
std::string new_filename = make_full_name(new_name);
bool result = file_worker.rename(old_filename.c_str(), new_filename.c_str());
remote->name = new_name;
return result;
}
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;
FileWorkerCpp file_worker;
if(!file_worker.mkdir(InfraredApp::infrared_directory)) return false;
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
FlipperFormat* ff = flipper_format_file_alloc(storage);
FURI_LOG_I("RemoteManager", "store file: \'%s\'", make_full_name(remote->name).c_str());
result = flipper_format_file_open_always(ff, make_full_name(remote->name).c_str());
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(const std::string& remote_name) {
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\'", make_full_name(remote_name).c_str());
result = flipper_format_file_open_existing(ff, make_full_name(remote_name).c_str());
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) {
remote = std::make_unique<InfraredAppRemote>(remote_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

@@ -0,0 +1,188 @@
/**
* @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 <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;
public:
/** Initialize new remote
*
* @param name - new remote name
*/
InfraredAppRemote(const std::string& name)
: name(name) {
}
};
/** Class to handle remote manager */
class InfraredAppRemoteManager {
/** Remote instance. There can be 1 remote loaded at a time. */
std::unique_ptr<InfraredAppRemote> remote;
/** Make full name from remote name
*
* @param remote_name name of remote
* @retval full name of remote on disk
*/
std::string make_full_name(const std::string& remote_name) const;
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
* @retval garanteed free remote name, prefixed with suggested
*/
std::string find_vacant_remote_name(const std::string& name);
/** 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 name - name of remote to load
* @retval true if success, false otherwise
*/
bool load(const std::string& name);
};

View File

@@ -0,0 +1,116 @@
#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

@@ -0,0 +1,134 @@
/**
* @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

@@ -0,0 +1,163 @@
#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* context) {
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

@@ -0,0 +1,164 @@
/**
* @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,9 @@
#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,305 @@
/**
* @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

@@ -0,0 +1,73 @@
#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* app) {
}

View File

@@ -0,0 +1,79 @@
#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 key", SubmenuIndexAddKey, submenu_callback, app);
submenu_add_item(submenu, "Rename key", SubmenuIndexRenameKey, submenu_callback, app);
submenu_add_item(submenu, "Delete key", SubmenuIndexDeleteKey, submenu_callback, app);
submenu_add_item(submenu, "Rename remote", SubmenuIndexRenameRemote, submenu_callback, app);
submenu_add_item(submenu, "Delete remote", SubmenuIndexDeleteRemote, submenu_callback, app);
submenu_set_selected_item(submenu, submenu_item_selected);
submenu_item_selected = 0;
view_manager->switch_to(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

@@ -0,0 +1,100 @@
#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, "Back");
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* app) {
}

View File

@@ -0,0 +1,38 @@
#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

@@ -0,0 +1,57 @@
#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 key:" : "Delete key:";
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

@@ -0,0 +1,72 @@
#include "../infrared_app.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 key");
} 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");
ValidatorIsFile* validator_is_file =
validator_is_file_alloc_init(app->infrared_directory, app->infrared_extension);
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
}
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

@@ -0,0 +1,31 @@
#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* app) {
}

View File

@@ -0,0 +1,75 @@
#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_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_red_blink();
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

@@ -0,0 +1,41 @@
#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

@@ -0,0 +1,60 @@
#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 key");
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* app) {
}

View File

@@ -0,0 +1,149 @@
#include <gui/modules/dialog_ex.h>
#include <file_worker_cpp.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;
FileWorkerCpp file_worker;
if(!button_pressed) {
if(file_worker.check_errors()) {
app->switch_to_next_scene(InfraredApp::Scene::LearnEnterName);
} else {
app->switch_to_previous_scene();
}
}
break;
}
case DialogExPressCenter:
if(!button_pressed) {
button_pressed = true;
app->notify_click_and_green_blink();
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

@@ -0,0 +1,134 @@
#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);
app->notify_click();
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);
app->notify_click();
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;
app->notify_click_and_green_blink();
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

@@ -0,0 +1,50 @@
#include "../infrared_app.h"
#include "infrared/infrared_app_event.h"
#include <text_store.h>
#include <file_worker_cpp.h>
void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) {
furi_assert(app);
FileWorkerCpp file_worker;
bool result = false;
bool file_select_result;
auto remote_manager = app->get_remote_manager();
auto last_selected_remote = remote_manager->get_remote_name();
const char* last_selected_remote_name =
last_selected_remote.size() ? last_selected_remote.c_str() : nullptr;
auto filename_ts =
std::make_unique<TextStore>(InfraredAppRemoteManager::max_remote_name_length);
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 = file_worker.file_select(
InfraredApp::infrared_directory,
InfraredApp::infrared_extension,
filename_ts->text,
filename_ts->text_size,
last_selected_remote_name);
if(file_select_result) {
if(remote_manager->load(std::string(filename_ts->text))) {
app->switch_to_next_scene(InfraredApp::Scene::Remote);
result = true;
}
}
if(!result) {
app->switch_to_previous_scene();
}
}
bool InfraredAppSceneRemoteList::on_event(InfraredApp* app, InfraredAppEvent* event) {
bool consumed = false;
return consumed;
}
void InfraredAppSceneRemoteList::on_exit(InfraredApp* app) {
}

View File

@@ -0,0 +1,66 @@
#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);
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

@@ -0,0 +1,57 @@
#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, "TV's", 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

@@ -0,0 +1,101 @@
#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 = {
.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();
InfraredAppEvent tick_event = {.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);
} 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

@@ -0,0 +1,123 @@
#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,121 @@
#include "furi/check.h"
#include "furi_hal_resources.h"
#include "assets_icons.h"
#include "gui/canvas.h"
#include "gui/view.h"
#include "input/input.h"
#include "m-string.h"
#include <gui/elements.h>
#include <furi.h>
#include "infrared_progress_view.h"
#include "gui/modules/button_panel.h"
#include <stdint.h>
struct InfraredProgressView {
View* view;
InfraredProgressViewBackCallback back_callback;
void* context;
};
typedef struct {
size_t progress;
size_t progress_total;
} InfraredProgressViewModel;
bool infrared_progress_view_increase_progress(InfraredProgressView* progress) {
furi_assert(progress);
bool result = false;
InfraredProgressViewModel* model = view_get_model(progress->view);
if(model->progress < model->progress_total) {
++model->progress;
result = model->progress < model->progress_total;
}
view_commit_model(progress->view, true);
return result;
}
static void infrared_progress_view_draw_callback(Canvas* canvas, void* _model) {
InfraredProgressViewModel* model = (InfraredProgressViewModel*)_model;
uint8_t x = 0;
uint8_t y = 36;
uint8_t width = 63;
uint8_t height = 59;
elements_bold_rounded_frame(canvas, x, y, width, height);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(
canvas, x + 34, y + 9, AlignCenter, AlignCenter, "Sending ...");
float progress_value = (float)model->progress / model->progress_total;
elements_progress_bar(canvas, x + 4, y + 19, width - 7, progress_value);
uint8_t percent_value = 100 * model->progress / model->progress_total;
char percents_string[10] = {0};
snprintf(percents_string, sizeof(percents_string), "%d%%", percent_value);
elements_multiline_text_aligned(
canvas, x + 33, y + 37, AlignCenter, AlignCenter, percents_string);
canvas_draw_icon(canvas, x + 11, y + height - 15, &I_Back_15x10);
canvas_draw_str(canvas, x + 30, y + height - 6, "= stop");
}
void infrared_progress_view_set_progress_total(
InfraredProgressView* progress,
uint16_t progress_total) {
furi_assert(progress);
InfraredProgressViewModel* model = view_get_model(progress->view);
model->progress = 0;
model->progress_total = progress_total;
view_commit_model(progress->view, false);
}
bool infrared_progress_view_input_callback(InputEvent* event, void* context) {
InfraredProgressView* instance = context;
if((event->type == InputTypeShort) && (event->key == InputKeyBack)) {
if(instance->back_callback) {
instance->back_callback(instance->context);
}
}
return true;
}
InfraredProgressView* infrared_progress_view_alloc(void) {
InfraredProgressView* instance = malloc(sizeof(InfraredProgressView));
instance->view = view_alloc();
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(InfraredProgressViewModel));
InfraredProgressViewModel* model = view_get_model(instance->view);
model->progress = 0;
model->progress_total = 0;
view_commit_model(instance->view, false);
view_set_draw_callback(instance->view, infrared_progress_view_draw_callback);
view_set_input_callback(instance->view, infrared_progress_view_input_callback);
view_set_context(instance->view, instance);
return instance;
}
void infrared_progress_view_free(InfraredProgressView* progress) {
view_free(progress->view);
free(progress);
}
void infrared_progress_view_set_back_callback(
InfraredProgressView* instance,
InfraredProgressViewBackCallback callback,
void* context) {
furi_assert(instance);
instance->back_callback = callback;
instance->context = context;
}
View* infrared_progress_view_get_view(InfraredProgressView* instance) {
furi_assert(instance);
furi_assert(instance->view);
return instance->view;
}

View File

@@ -0,0 +1,68 @@
/**
* @file infrared_progress_view.h
* Infrared: Custom Infrared view module.
* It shows popup progress bar during brute force.
*/
#pragma once
#include <gui/view.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Anonumous instance */
typedef struct InfraredProgressView InfraredProgressView;
/** Callback for back button handling */
typedef void (*InfraredProgressViewBackCallback)(void*);
/** Allocate and initialize Infrared view
*
* @retval new allocated instance
*/
InfraredProgressView* infrared_progress_view_alloc();
/** Free previously allocated Progress view module instance
*
* @param instance to free
*/
void infrared_progress_view_free(InfraredProgressView* instance);
/** Get progress view module view
*
* @param instance view module
* @retval view
*/
View* infrared_progress_view_get_view(InfraredProgressView* instance);
/** Increase progress on progress view module
*
* @param instance view module
* @retval true - value is incremented and maximum is reached,
* false - value is incremented and maximum is not reached
*/
bool infrared_progress_view_increase_progress(InfraredProgressView* instance);
/** Set maximum progress value
*
* @param instance - view module
* @param progress_max - maximum value of progress
*/
void infrared_progress_view_set_progress_total(
InfraredProgressView* instance,
uint16_t progress_max);
/** Set back button callback
*
* @param instance - view module
* @param callback - callback to call for back button
* @param context - context to pass to callback
*/
void infrared_progress_view_set_back_callback(
InfraredProgressView* instance,
InfraredProgressViewBackCallback callback,
void* context);
#ifdef __cplusplus
}
#endif