signal_file.c 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /* Copyright (C) 2023 Salvatore Sanfilippo -- All Rights Reserved
  2. * Copyright (C) 2023 Maciej Wojtasik -- All Rights Reserved
  3. * See the LICENSE file for information about the license. */
  4. #include "app.h"
  5. #include <stream/stream.h>
  6. #include <flipper_format/flipper_format_i.h>
  7. /* ========================= Signal file operations ========================= */
  8. /* This function saves the current logical signal on disk. What is saved here
  9. * is not the signal as level and duration as we received it from CC1101,
  10. * but it's logical representation stored in the app->msg_info bitmap, where
  11. * each 1 or 0 means a puls or gap for the specified short pulse duration time
  12. * (te). */
  13. bool save_signal(ProtoViewApp *app, const char *filename) {
  14. /* We have a message at all? */
  15. if (app->msg_info == NULL || app->msg_info->pulses_count == 0) return false;
  16. Storage *storage = furi_record_open(RECORD_STORAGE);
  17. FlipperFormat *file = flipper_format_file_alloc(storage);
  18. Stream *stream = flipper_format_get_raw_stream(file);
  19. FuriString *file_content = NULL;
  20. bool success = true;
  21. if (flipper_format_file_open_always(file, filename)) {
  22. /* Write the file header. */
  23. FuriString *file_content = furi_string_alloc();
  24. const char *preset_id = ProtoViewModulations[app->modulation].id;
  25. furi_string_printf(file_content,
  26. "Filetype: Flipper SubGhz RAW File\n"
  27. "Version: 1\n"
  28. "Frequency: %ld\n"
  29. "Preset: %s\n",
  30. app->frequency,
  31. preset_id ? preset_id : "FuriHalSubGhzPresetCustom");
  32. /* For custom modulations, we need to emit a set of registers. */
  33. if (preset_id == NULL) {
  34. FuriString *custom = furi_string_alloc();
  35. uint8_t *regs = ProtoViewModulations[app->modulation].custom;
  36. furi_string_printf(custom,
  37. "Custom_preset_module: CC1101\n"
  38. "Custom_preset_data: ");
  39. /* We will know the size of the preset data once we reach the end
  40. * of the registers (null address). For now it's INT_MAX. */
  41. int preset_data_size = INT_MAX;
  42. bool patable_reached = false;
  43. for(int j = 0; j <= preset_data_size; j += 2) {
  44. // End reached, set the size to write the remaining 8 bytes (PATABLE)
  45. if(!patable_reached && regs[j] == 0) {
  46. preset_data_size = j + 8;
  47. patable_reached = true;
  48. }
  49. furi_string_cat_printf(custom, "%02X %02X ",
  50. (int)regs[j], (int)regs[j+1]);
  51. }
  52. size_t len = furi_string_size(custom);
  53. furi_string_set_char(custom,len-1,'\n');
  54. furi_string_cat(file_content,custom);
  55. furi_string_free(custom);
  56. }
  57. /* We always save raw files. */
  58. furi_string_cat_printf(file_content,
  59. "Protocol: RAW\n"
  60. "RAW_Data: -10000\n"); // Start with 10 ms of gap
  61. /* Write header. */
  62. size_t len = furi_string_size(file_content);
  63. if (stream_write(stream,
  64. (uint8_t*) furi_string_get_cstr(file_content), len)
  65. != len)
  66. {
  67. FURI_LOG_W(TAG, "Short write to file");
  68. success = false;
  69. goto write_err;
  70. }
  71. furi_string_reset(file_content);
  72. /* Write raw data sections. The Flipper subghz parser can't handle
  73. * too much data on a single line, so we generate a new one
  74. * every few samples. */
  75. uint32_t this_line_samples = 0;
  76. uint32_t max_line_samples = 100;
  77. uint32_t idx = 0; // Iindex in the signal bitmap.
  78. ProtoViewMsgInfo *i = app->msg_info;
  79. while(idx < i->pulses_count) {
  80. bool level = bitmap_get(i->bits,i->bits_bytes,idx);
  81. uint32_t te_times = 1;
  82. idx++;
  83. /* Count the duration of the current pulse/gap. */
  84. while(idx < i->pulses_count &&
  85. bitmap_get(i->bits,i->bits_bytes,idx) == level)
  86. {
  87. te_times++;
  88. idx++;
  89. }
  90. // Invariant: after the loop 'idx' is at the start of the
  91. // next gap or pulse.
  92. int32_t dur = (int32_t)i->short_pulse_dur * te_times;
  93. if (level == 0) dur = -dur; /* Negative is gap in raw files. */
  94. /* Emit the sample. If this is the first sample of the line,
  95. * also emit the RAW_Data: field. */
  96. if (this_line_samples == 0)
  97. furi_string_cat_printf(file_content,"RAW_Data: ");
  98. furi_string_cat_printf(file_content,"%d ",(int)dur);
  99. this_line_samples++;
  100. /* Store the current set of samples on disk, when we reach a
  101. * given number or the end of the signal. */
  102. bool end_reached = (idx == i->pulses_count);
  103. if (this_line_samples == max_line_samples || end_reached) {
  104. /* If that's the end, terminate the signal with a long
  105. * gap. */
  106. if (end_reached) furi_string_cat_printf(file_content,"-10000 ");
  107. /* We always have a trailing space in the last sample. Make it
  108. * a newline. */
  109. size_t len = furi_string_size(file_content);
  110. furi_string_set_char(file_content,len-1,'\n');
  111. if (stream_write(stream,
  112. (uint8_t*) furi_string_get_cstr(file_content),
  113. len) != len)
  114. {
  115. FURI_LOG_W(TAG, "Short write to file");
  116. success = false;
  117. goto write_err;
  118. }
  119. /* Prepare for next line. */
  120. furi_string_reset(file_content);
  121. this_line_samples = 0;
  122. }
  123. }
  124. } else {
  125. success = false;
  126. FURI_LOG_W(TAG, "Unable to open file");
  127. }
  128. write_err:
  129. furi_record_close(RECORD_STORAGE);
  130. flipper_format_free(file);
  131. if (file_content != NULL) furi_string_free(file_content);
  132. return success;
  133. }