subghz_remote_app_i.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. #include "subghz_remote_app_i.h"
  2. #include <lib/toolbox/path.h>
  3. #include <flipper_format/flipper_format_i.h>
  4. #include "helpers/txrx/subghz_txrx.h"
  5. #ifndef FW_ORIGIN_Official
  6. #include <lib/subghz/blocks/custom_btn.h>
  7. #endif
  8. #define TAG "SubGhzRemote"
  9. static const char* map_file_labels[SubRemSubKeyNameMaxCount][2] = {
  10. [SubRemSubKeyNameUp] = {"UP", "ULABEL"},
  11. [SubRemSubKeyNameDown] = {"DOWN", "DLABEL"},
  12. [SubRemSubKeyNameLeft] = {"LEFT", "LLABEL"},
  13. [SubRemSubKeyNameRight] = {"RIGHT", "RLABEL"},
  14. [SubRemSubKeyNameOk] = {"OK", "OKLABEL"},
  15. };
  16. void subrem_map_preset_reset(SubRemMapPreset* map_preset) {
  17. furi_assert(map_preset);
  18. for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) {
  19. subrem_sub_file_preset_reset(map_preset->subs_preset[i]);
  20. }
  21. }
  22. static SubRemLoadMapState subrem_map_preset_check(
  23. SubRemMapPreset* map_preset,
  24. SubGhzTxRx* txrx,
  25. FlipperFormat* fff_data_file) {
  26. furi_assert(map_preset);
  27. furi_assert(txrx);
  28. bool all_loaded = true;
  29. SubRemLoadMapState ret = SubRemLoadMapStateErrorBrokenFile;
  30. SubRemLoadSubState sub_loadig_state;
  31. SubRemSubFilePreset* sub_preset;
  32. for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) {
  33. sub_preset = map_preset->subs_preset[i];
  34. sub_loadig_state = SubRemLoadSubStateErrorNoFile;
  35. if(furi_string_empty(sub_preset->file_path)) {
  36. // FURI_LOG_I(TAG, "Empty file path");
  37. } else if(!flipper_format_file_open_existing(
  38. fff_data_file, furi_string_get_cstr(sub_preset->file_path))) {
  39. sub_preset->load_state = SubRemLoadSubStateErrorNoFile;
  40. FURI_LOG_W(TAG, "Error open file %s", furi_string_get_cstr(sub_preset->file_path));
  41. } else {
  42. sub_loadig_state = subrem_sub_preset_load(sub_preset, txrx, fff_data_file);
  43. }
  44. if(sub_loadig_state != SubRemLoadSubStateOK) {
  45. all_loaded = false;
  46. } else {
  47. ret = SubRemLoadMapStateNotAllOK;
  48. }
  49. if(ret != SubRemLoadMapStateErrorBrokenFile && all_loaded) {
  50. ret = SubRemLoadMapStateOK;
  51. }
  52. flipper_format_file_close(fff_data_file);
  53. }
  54. return ret;
  55. }
  56. static bool subrem_map_preset_load(SubRemMapPreset* map_preset, FlipperFormat* fff_data_file) {
  57. furi_assert(map_preset);
  58. bool ret = false;
  59. SubRemSubFilePreset* sub_preset;
  60. for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) {
  61. sub_preset = map_preset->subs_preset[i];
  62. if(!flipper_format_read_string(
  63. fff_data_file, map_file_labels[i][0], sub_preset->file_path)) {
  64. #ifdef FURI_DEBUG
  65. FURI_LOG_W(TAG, "No file patch for %s", map_file_labels[i][0]);
  66. #endif
  67. sub_preset->type = SubGhzProtocolTypeUnknown;
  68. } else if(!path_contains_only_ascii(furi_string_get_cstr(sub_preset->file_path))) {
  69. FURI_LOG_E(TAG, "Incorrect characters in [%s] file path", map_file_labels[i][0]);
  70. sub_preset->type = SubGhzProtocolTypeUnknown;
  71. } else if(!flipper_format_rewind(fff_data_file)) {
  72. // Rewind error
  73. } else if(!flipper_format_read_string(
  74. fff_data_file, map_file_labels[i][1], sub_preset->label)) {
  75. #ifdef FURI_DEBUG
  76. FURI_LOG_W(TAG, "No Label for %s", map_file_labels[i][0]);
  77. #endif
  78. ret = true;
  79. } else {
  80. ret = true;
  81. }
  82. if(ret) {
  83. // Preload seccesful
  84. FURI_LOG_I(
  85. TAG,
  86. "%-5s: %s %s",
  87. map_file_labels[i][0],
  88. furi_string_get_cstr(sub_preset->label),
  89. furi_string_get_cstr(sub_preset->file_path));
  90. sub_preset->load_state = SubRemLoadSubStatePreloaded;
  91. }
  92. flipper_format_rewind(fff_data_file);
  93. }
  94. return ret;
  95. }
  96. SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path) {
  97. furi_assert(app);
  98. furi_assert(file_path);
  99. #ifdef FURI_DEBUG
  100. FURI_LOG_I(TAG, "Load Map File Start");
  101. #endif
  102. Storage* storage = furi_record_open(RECORD_STORAGE);
  103. FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
  104. SubRemLoadMapState ret = SubRemLoadMapStateErrorOpenError;
  105. #ifdef FURI_DEBUG
  106. FURI_LOG_I(TAG, "Open Map File..");
  107. #endif
  108. subrem_map_preset_reset(app->map_preset);
  109. if(!flipper_format_file_open_existing(fff_data_file, file_path)) {
  110. FURI_LOG_E(TAG, "Could not open MAP file %s", file_path);
  111. ret = SubRemLoadMapStateErrorOpenError;
  112. } else {
  113. if(!subrem_map_preset_load(app->map_preset, fff_data_file)) {
  114. FURI_LOG_E(TAG, "Could no Sub file path in MAP file");
  115. // ret = // error for popup
  116. } else if(!flipper_format_file_close(fff_data_file)) {
  117. ret = SubRemLoadMapStateErrorOpenError;
  118. } else {
  119. ret = subrem_map_preset_check(app->map_preset, app->txrx, fff_data_file);
  120. }
  121. }
  122. if(ret == SubRemLoadMapStateOK) {
  123. FURI_LOG_I(TAG, "Load Map File Seccesful");
  124. } else if(ret == SubRemLoadMapStateNotAllOK) {
  125. FURI_LOG_I(TAG, "Load Map File Seccesful [Not all files]");
  126. } else {
  127. FURI_LOG_E(TAG, "Broken Map File");
  128. }
  129. flipper_format_file_close(fff_data_file);
  130. flipper_format_free(fff_data_file);
  131. furi_record_close(RECORD_STORAGE);
  132. return ret;
  133. }
  134. bool subrem_save_protocol_to_file(FlipperFormat* flipper_format, const char* sub_file_name) {
  135. furi_assert(flipper_format);
  136. furi_assert(sub_file_name);
  137. Storage* storage = furi_record_open(RECORD_STORAGE);
  138. Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format);
  139. bool saved = false;
  140. uint32_t repeat = 200;
  141. FuriString* file_dir = furi_string_alloc();
  142. path_extract_dirname(sub_file_name, file_dir);
  143. do {
  144. // removing additional fields
  145. flipper_format_delete_key(flipper_format, "Repeat");
  146. // flipper_format_delete_key(flipper_format, "Manufacture");
  147. if(!storage_simply_remove(storage, sub_file_name)) {
  148. break;
  149. }
  150. //ToDo check Write
  151. stream_seek(flipper_format_stream, 0, StreamOffsetFromStart);
  152. stream_save_to_file(flipper_format_stream, storage, sub_file_name, FSOM_CREATE_ALWAYS);
  153. if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
  154. FURI_LOG_E(TAG, "Unable Repeat");
  155. break;
  156. }
  157. saved = true;
  158. } while(0);
  159. furi_string_free(file_dir);
  160. furi_record_close(RECORD_STORAGE);
  161. return saved;
  162. }
  163. void subrem_save_active_sub(void* context) {
  164. furi_assert(context);
  165. SubGhzRemoteApp* app = context;
  166. SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chosen_sub];
  167. subrem_save_protocol_to_file(
  168. sub_preset->fff_data, furi_string_get_cstr(sub_preset->file_path));
  169. }
  170. bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset) {
  171. furi_assert(app);
  172. furi_assert(sub_preset);
  173. bool ret = false;
  174. subrem_tx_stop_sub(app, true);
  175. if(sub_preset->type == SubGhzProtocolTypeUnknown) {
  176. ret = false;
  177. } else {
  178. FURI_LOG_I(TAG, "Send %s", furi_string_get_cstr(sub_preset->label));
  179. subghz_txrx_load_decoder_by_name_protocol(
  180. app->txrx, furi_string_get_cstr(sub_preset->protocaol_name));
  181. subghz_txrx_set_preset(
  182. app->txrx,
  183. furi_string_get_cstr(sub_preset->freq_preset.name),
  184. sub_preset->freq_preset.frequency,
  185. NULL,
  186. 0);
  187. #ifndef FW_ORIGIN_Official
  188. subghz_custom_btns_reset();
  189. #endif
  190. if(subghz_txrx_tx_start(app->txrx, sub_preset->fff_data) == SubGhzTxRxStartTxStateOk) {
  191. ret = true;
  192. }
  193. }
  194. return ret;
  195. }
  196. bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced) {
  197. furi_assert(app);
  198. SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chosen_sub];
  199. if(forced || (sub_preset->type != SubGhzProtocolTypeRAW)) {
  200. subghz_txrx_stop(app->txrx);
  201. #ifndef FW_ORIGIN_Official
  202. if(sub_preset->type == SubGhzProtocolTypeDynamic) {
  203. subghz_txrx_reset_dynamic_and_custom_btns(app->txrx);
  204. }
  205. subghz_custom_btns_reset();
  206. #endif
  207. return true;
  208. }
  209. return false;
  210. }
  211. SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) {
  212. furi_assert(app);
  213. FuriString* file_path = furi_string_alloc();
  214. SubRemLoadMapState ret = SubRemLoadMapStateBack;
  215. DialogsFileBrowserOptions browser_options;
  216. dialog_file_browser_set_basic_options(&browser_options, SUBREM_APP_EXTENSION, &I_subrem_10px);
  217. browser_options.base_path = SUBREM_APP_FOLDER;
  218. // Input events and views are managed by file_select
  219. if(!dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) {
  220. } else {
  221. ret = subrem_map_file_load(app, furi_string_get_cstr(app->file_path));
  222. }
  223. furi_string_free(file_path);
  224. return ret;
  225. }
  226. bool subrem_save_map_to_file(SubGhzRemoteApp* app) {
  227. furi_assert(app);
  228. const char* file_name = furi_string_get_cstr(app->file_path);
  229. bool saved = false;
  230. FlipperFormat* fff_data = flipper_format_string_alloc();
  231. SubRemSubFilePreset* sub_preset;
  232. flipper_format_write_header_cstr(
  233. fff_data, SUBREM_APP_APP_FILE_TYPE, SUBREM_APP_APP_FILE_VERSION);
  234. for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) {
  235. sub_preset = app->map_preset->subs_preset[i];
  236. if(!furi_string_empty(sub_preset->file_path)) {
  237. flipper_format_write_string(fff_data, map_file_labels[i][0], sub_preset->file_path);
  238. }
  239. }
  240. for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) {
  241. sub_preset = app->map_preset->subs_preset[i];
  242. if(!furi_string_empty(sub_preset->file_path)) {
  243. flipper_format_write_string(fff_data, map_file_labels[i][1], sub_preset->label);
  244. }
  245. }
  246. Storage* storage = furi_record_open(RECORD_STORAGE);
  247. Stream* flipper_format_stream = flipper_format_get_raw_stream(fff_data);
  248. do {
  249. if(!storage_simply_remove(storage, file_name)) {
  250. break;
  251. }
  252. //ToDo check Write
  253. stream_seek(flipper_format_stream, 0, StreamOffsetFromStart);
  254. stream_save_to_file(flipper_format_stream, storage, file_name, FSOM_CREATE_ALWAYS);
  255. saved = true;
  256. } while(0);
  257. furi_record_close(RECORD_STORAGE);
  258. flipper_format_free(fff_data);
  259. return saved;
  260. }