2023-05-18 10:48:25 +00:00
|
|
|
#include "subghz_remote_app_i.h"
|
|
|
|
#include <lib/toolbox/path.h>
|
|
|
|
#include <flipper_format/flipper_format_i.h>
|
|
|
|
|
2023-05-30 13:51:07 +00:00
|
|
|
#include "helpers/txrx/subghz_txrx.h"
|
2023-05-18 10:48:25 +00:00
|
|
|
|
|
|
|
// #include <lib/subghz/protocols/keeloq.h>
|
|
|
|
// #include <lib/subghz/protocols/star_line.h>
|
|
|
|
|
2023-05-30 13:51:07 +00:00
|
|
|
#ifdef APP_SUBGHZREMOTE
|
|
|
|
#include <lib/subghz/protocols/protocol_items.h>
|
2023-05-18 10:48:25 +00:00
|
|
|
#include <lib/subghz/blocks/custom_btn.h>
|
2023-05-30 13:51:07 +00:00
|
|
|
#endif
|
2023-05-18 10:48:25 +00:00
|
|
|
|
|
|
|
#define TAG "SubGhzRemote"
|
|
|
|
|
|
|
|
static const char* map_file_labels[SubRemSubKeyNameMaxCount][2] = {
|
|
|
|
[SubRemSubKeyNameUp] = {"UP", "ULABEL"},
|
|
|
|
[SubRemSubKeyNameDown] = {"DOWN", "DLABEL"},
|
|
|
|
[SubRemSubKeyNameLeft] = {"LEFT", "LLABEL"},
|
|
|
|
[SubRemSubKeyNameRight] = {"RIGHT", "RLABEL"},
|
|
|
|
[SubRemSubKeyNameOk] = {"OK", "OKLABEL"},
|
|
|
|
};
|
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
static void subrem_map_preset_reset(SubRemMapPreset* map_preset) {
|
|
|
|
furi_assert(map_preset);
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) {
|
|
|
|
subrem_sub_file_preset_reset(map_preset->subs_preset[i]);
|
2023-05-18 10:48:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
static SubRemLoadMapState subrem_map_preset_check(
|
|
|
|
SubRemMapPreset* map_preset,
|
|
|
|
SubGhzTxRx* txrx,
|
|
|
|
FlipperFormat* fff_data_file) {
|
2023-05-21 07:29:58 +00:00
|
|
|
furi_assert(map_preset);
|
2023-05-21 20:34:42 +00:00
|
|
|
furi_assert(txrx);
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
bool all_loaded = true;
|
|
|
|
SubRemLoadMapState ret = SubRemLoadMapStateErrorBrokenFile;
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-30 13:51:07 +00:00
|
|
|
SubRemLoadSubState sub_loadig_state;
|
2023-05-21 20:34:42 +00:00
|
|
|
SubRemSubFilePreset* sub_preset;
|
2023-05-18 10:48:25 +00:00
|
|
|
|
|
|
|
for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) {
|
2023-05-21 20:34:42 +00:00
|
|
|
sub_preset = map_preset->subs_preset[i];
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-30 13:51:07 +00:00
|
|
|
sub_loadig_state = SubRemLoadSubStateErrorNoFile;
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
if(furi_string_empty(sub_preset->file_path)) {
|
|
|
|
// FURI_LOG_I(TAG, "Empty file path");
|
|
|
|
} else if(!flipper_format_file_open_existing(
|
|
|
|
fff_data_file, furi_string_get_cstr(sub_preset->file_path))) {
|
2023-05-30 13:51:07 +00:00
|
|
|
sub_preset->load_state = SubRemLoadSubStateErrorNoFile;
|
2023-05-21 20:34:42 +00:00
|
|
|
FURI_LOG_W(TAG, "Error open file %s", furi_string_get_cstr(sub_preset->file_path));
|
|
|
|
} else {
|
2023-05-30 13:51:07 +00:00
|
|
|
sub_loadig_state = subrem_sub_preset_load(sub_preset, txrx, fff_data_file);
|
2023-05-21 20:34:42 +00:00
|
|
|
}
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-30 13:51:07 +00:00
|
|
|
if(sub_loadig_state != SubRemLoadSubStateOK) {
|
2023-05-21 20:34:42 +00:00
|
|
|
all_loaded = false;
|
|
|
|
} else {
|
|
|
|
ret = SubRemLoadMapStateNotAllOK;
|
|
|
|
}
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
if(ret != SubRemLoadMapStateErrorBrokenFile && all_loaded) {
|
|
|
|
ret = SubRemLoadMapStateOK;
|
|
|
|
}
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
flipper_format_file_close(fff_data_file);
|
2023-05-18 10:48:25 +00:00
|
|
|
}
|
2023-05-21 20:34:42 +00:00
|
|
|
|
|
|
|
return ret;
|
2023-05-18 10:48:25 +00:00
|
|
|
}
|
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
static bool subrem_map_preset_load(SubRemMapPreset* map_preset, FlipperFormat* fff_data_file) {
|
|
|
|
furi_assert(map_preset);
|
2023-05-18 10:48:25 +00:00
|
|
|
bool ret = false;
|
|
|
|
SubRemSubFilePreset* sub_preset;
|
|
|
|
for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) {
|
2023-05-21 20:34:42 +00:00
|
|
|
sub_preset = map_preset->subs_preset[i];
|
2023-05-18 10:48:25 +00:00
|
|
|
if(!flipper_format_read_string(
|
|
|
|
fff_data_file, map_file_labels[i][0], sub_preset->file_path)) {
|
|
|
|
#if FURI_DEBUG
|
|
|
|
FURI_LOG_W(TAG, "No file patch for %s", map_file_labels[i][0]);
|
|
|
|
#endif
|
|
|
|
sub_preset->type = SubGhzProtocolTypeUnknown;
|
2023-05-30 13:51:07 +00:00
|
|
|
} else if(!path_contains_only_ascii(furi_string_get_cstr(sub_preset->file_path))) {
|
|
|
|
FURI_LOG_E(TAG, "Incorrect characters in [%s] file path", map_file_labels[i][0]);
|
|
|
|
sub_preset->type = SubGhzProtocolTypeUnknown;
|
2023-05-18 10:48:25 +00:00
|
|
|
} else if(!flipper_format_rewind(fff_data_file)) {
|
|
|
|
// Rewind error
|
|
|
|
} else if(!flipper_format_read_string(
|
|
|
|
fff_data_file, map_file_labels[i][1], sub_preset->label)) {
|
|
|
|
#if FURI_DEBUG
|
|
|
|
FURI_LOG_W(TAG, "No Label for %s", map_file_labels[i][0]);
|
|
|
|
#endif
|
2023-05-21 20:34:42 +00:00
|
|
|
ret = true;
|
2023-05-18 10:48:25 +00:00
|
|
|
} else {
|
2023-05-21 20:34:42 +00:00
|
|
|
ret = true;
|
|
|
|
}
|
|
|
|
if(ret) {
|
2023-05-19 20:21:18 +00:00
|
|
|
// Preload seccesful
|
2023-05-18 10:48:25 +00:00
|
|
|
FURI_LOG_I(
|
|
|
|
TAG,
|
|
|
|
"%-5s: %s %s",
|
|
|
|
map_file_labels[i][0],
|
|
|
|
furi_string_get_cstr(sub_preset->label),
|
|
|
|
furi_string_get_cstr(sub_preset->file_path));
|
2023-05-19 20:21:18 +00:00
|
|
|
sub_preset->load_state = SubRemLoadSubStatePreloaded;
|
2023-05-18 10:48:25 +00:00
|
|
|
}
|
2023-05-19 20:21:18 +00:00
|
|
|
|
2023-05-18 10:48:25 +00:00
|
|
|
flipper_format_rewind(fff_data_file);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-05-19 20:21:18 +00:00
|
|
|
SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path) {
|
2023-05-18 10:48:25 +00:00
|
|
|
furi_assert(app);
|
|
|
|
furi_assert(file_path);
|
|
|
|
#if FURI_DEBUG
|
|
|
|
FURI_LOG_I(TAG, "Load Map File Start");
|
|
|
|
#endif
|
|
|
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
|
|
|
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
|
2023-05-19 20:21:18 +00:00
|
|
|
SubRemLoadMapState ret = SubRemLoadMapStateErrorOpenError;
|
2023-05-18 10:48:25 +00:00
|
|
|
#if FURI_DEBUG
|
|
|
|
FURI_LOG_I(TAG, "Open Map File..");
|
|
|
|
#endif
|
2023-05-21 07:29:58 +00:00
|
|
|
subrem_map_preset_reset(app->map_preset);
|
2023-05-18 10:48:25 +00:00
|
|
|
|
|
|
|
if(!flipper_format_file_open_existing(fff_data_file, file_path)) {
|
|
|
|
FURI_LOG_E(TAG, "Could not open MAP file %s", file_path);
|
2023-05-19 20:21:18 +00:00
|
|
|
ret = SubRemLoadMapStateErrorOpenError;
|
2023-05-18 10:48:25 +00:00
|
|
|
} else {
|
2023-05-21 20:34:42 +00:00
|
|
|
if(!subrem_map_preset_load(app->map_preset, fff_data_file)) {
|
2023-05-18 10:48:25 +00:00
|
|
|
FURI_LOG_E(TAG, "Could no Sub file path in MAP file");
|
|
|
|
// ret = // error for popup
|
2023-05-19 20:21:18 +00:00
|
|
|
} else if(!flipper_format_file_close(fff_data_file)) {
|
|
|
|
ret = SubRemLoadMapStateErrorOpenError;
|
|
|
|
} else {
|
2023-05-21 20:34:42 +00:00
|
|
|
ret = subrem_map_preset_check(app->map_preset, app->txrx, fff_data_file);
|
2023-05-18 10:48:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-19 20:21:18 +00:00
|
|
|
if(ret == SubRemLoadMapStateOK) {
|
|
|
|
FURI_LOG_I(TAG, "Load Map File Seccesful");
|
|
|
|
} else if(ret == SubRemLoadMapStateNotAllOK) {
|
|
|
|
FURI_LOG_I(TAG, "Load Map File Seccesful [Not all files]");
|
|
|
|
} else {
|
2023-05-18 10:48:25 +00:00
|
|
|
FURI_LOG_E(TAG, "Broken Map File");
|
|
|
|
}
|
|
|
|
|
|
|
|
flipper_format_file_close(fff_data_file);
|
|
|
|
flipper_format_free(fff_data_file);
|
|
|
|
|
|
|
|
furi_record_close(RECORD_STORAGE);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
bool subrem_save_protocol_to_file(FlipperFormat* flipper_format, const char* sub_file_name) {
|
2023-05-18 10:48:25 +00:00
|
|
|
furi_assert(flipper_format);
|
2023-05-21 20:34:42 +00:00
|
|
|
furi_assert(sub_file_name);
|
2023-05-18 10:48:25 +00:00
|
|
|
|
|
|
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
|
|
|
Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format);
|
|
|
|
|
|
|
|
bool saved = false;
|
2023-05-19 09:32:35 +00:00
|
|
|
uint32_t repeat = 200;
|
2023-05-18 10:48:25 +00:00
|
|
|
FuriString* file_dir = furi_string_alloc();
|
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
path_extract_dirname(sub_file_name, file_dir);
|
2023-05-18 10:48:25 +00:00
|
|
|
do {
|
2023-05-19 09:32:35 +00:00
|
|
|
// removing additional fields
|
2023-05-18 10:48:25 +00:00
|
|
|
flipper_format_delete_key(flipper_format, "Repeat");
|
2023-05-19 09:32:35 +00:00
|
|
|
// flipper_format_delete_key(flipper_format, "Manufacture");
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
if(!storage_simply_remove(storage, sub_file_name)) {
|
2023-05-18 10:48:25 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-05-19 09:32:35 +00:00
|
|
|
|
2023-05-18 10:48:25 +00:00
|
|
|
//ToDo check Write
|
|
|
|
stream_seek(flipper_format_stream, 0, StreamOffsetFromStart);
|
2023-05-21 20:34:42 +00:00
|
|
|
stream_save_to_file(flipper_format_stream, storage, sub_file_name, FSOM_CREATE_ALWAYS);
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-19 09:32:35 +00:00
|
|
|
if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
|
|
|
|
FURI_LOG_E(TAG, "Unable Repeat");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2023-05-18 10:48:25 +00:00
|
|
|
saved = true;
|
|
|
|
} while(0);
|
2023-05-19 09:32:35 +00:00
|
|
|
|
2023-05-18 10:48:25 +00:00
|
|
|
furi_string_free(file_dir);
|
|
|
|
furi_record_close(RECORD_STORAGE);
|
|
|
|
return saved;
|
|
|
|
}
|
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
void subrem_save_active_sub(void* context) {
|
|
|
|
furi_assert(context);
|
|
|
|
SubGhzRemoteApp* app = context;
|
|
|
|
|
|
|
|
SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chusen_sub];
|
|
|
|
subrem_save_protocol_to_file(
|
|
|
|
sub_preset->fff_data, furi_string_get_cstr(sub_preset->file_path));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset) {
|
2023-05-18 10:48:25 +00:00
|
|
|
furi_assert(app);
|
|
|
|
furi_assert(sub_preset);
|
|
|
|
bool ret = false;
|
|
|
|
|
|
|
|
subrem_tx_stop_sub(app, true);
|
|
|
|
|
|
|
|
if(sub_preset->type == SubGhzProtocolTypeUnknown) {
|
2023-05-21 20:34:42 +00:00
|
|
|
ret = false;
|
|
|
|
} else {
|
|
|
|
FURI_LOG_I(TAG, "Send %s", furi_string_get_cstr(sub_preset->label));
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
subghz_txrx_load_decoder_by_name_protocol(
|
|
|
|
app->txrx, furi_string_get_cstr(sub_preset->protocaol_name));
|
2023-05-18 10:48:25 +00:00
|
|
|
|
|
|
|
subghz_custom_btns_reset();
|
2023-05-21 20:34:42 +00:00
|
|
|
subghz_txrx_set_preset(
|
|
|
|
app->txrx,
|
|
|
|
furi_string_get_cstr(sub_preset->freq_preset.name),
|
|
|
|
sub_preset->freq_preset.frequency,
|
|
|
|
NULL,
|
|
|
|
0);
|
2023-05-21 07:29:58 +00:00
|
|
|
|
2023-05-30 13:51:07 +00:00
|
|
|
#ifdef APP_SUBGHZREMOTE
|
2023-05-21 20:34:42 +00:00
|
|
|
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK);
|
|
|
|
keeloq_reset_original_btn();
|
|
|
|
subghz_custom_btns_reset();
|
2023-05-30 13:51:07 +00:00
|
|
|
#endif
|
2023-05-18 10:48:25 +00:00
|
|
|
|
2023-05-21 20:34:42 +00:00
|
|
|
if(subghz_txrx_tx_start(app->txrx, sub_preset->fff_data) == SubGhzTxRxStartTxStateOk) {
|
2023-05-18 10:48:25 +00:00
|
|
|
ret = true;
|
|
|
|
}
|
2023-05-21 20:34:42 +00:00
|
|
|
}
|
2023-05-18 10:48:25 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced) {
|
|
|
|
furi_assert(app);
|
2023-05-21 07:29:58 +00:00
|
|
|
SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chusen_sub];
|
2023-05-18 10:48:25 +00:00
|
|
|
|
|
|
|
if(forced || (sub_preset->type != SubGhzProtocolTypeRAW)) {
|
2023-05-30 13:51:07 +00:00
|
|
|
subghz_txrx_stop(app->txrx);
|
|
|
|
#ifdef APP_SUBGHZREMOTE
|
|
|
|
if(sub_preset->type == SubGhzProtocolTypeDynamic) {
|
2023-05-19 22:15:36 +00:00
|
|
|
subghz_environment_reset_keeloq(app->environment);
|
2023-05-18 10:48:25 +00:00
|
|
|
}
|
2023-05-30 14:41:49 +00:00
|
|
|
keeloq_reset_original_btn();
|
|
|
|
subghz_custom_btns_reset();
|
2023-05-18 10:48:25 +00:00
|
|
|
#endif
|
2023-05-30 13:51:07 +00:00
|
|
|
return true;
|
2023-05-18 10:48:25 +00:00
|
|
|
}
|
|
|
|
|
2023-05-21 07:29:58 +00:00
|
|
|
return false;
|
2023-05-18 10:48:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) {
|
|
|
|
furi_assert(app);
|
|
|
|
|
|
|
|
FuriString* file_path = furi_string_alloc();
|
|
|
|
SubRemLoadMapState ret = SubRemLoadMapStateBack;
|
|
|
|
|
|
|
|
DialogsFileBrowserOptions browser_options;
|
2023-05-30 13:51:07 +00:00
|
|
|
dialog_file_browser_set_basic_options(&browser_options, SUBREM_APP_EXTENSION, &I_subrem_10px);
|
2023-05-18 10:48:25 +00:00
|
|
|
browser_options.base_path = SUBREM_APP_FOLDER;
|
|
|
|
|
|
|
|
// Input events and views are managed by file_select
|
|
|
|
if(!dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) {
|
|
|
|
} else {
|
2023-05-19 20:21:18 +00:00
|
|
|
ret = subrem_map_file_load(app, furi_string_get_cstr(app->file_path));
|
2023-05-18 10:48:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
furi_string_free(file_path);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|