action_qpl.c 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // Methods for Quac Playlist transmission
  2. #include <toolbox/stream/stream.h>
  3. #include <toolbox/stream/file_stream.h>
  4. #include <toolbox/path.h>
  5. #include <toolbox/args.h>
  6. #include <notification/notification_messages.h>
  7. #include "action_i.h"
  8. #include "quac.h"
  9. /** Open the Playlist file and then transmit each action
  10. *
  11. * Each line of the playlist file is one of:
  12. * <file_path>
  13. * Full SD card path, or relative path to action to be transmitted. Must be
  14. * one of the supported filetypes (.sub, .rfid, [.ir coming soon])
  15. *
  16. * If an .rfid file has a space followed by a number, that will be the
  17. * duration for that RFID transmission. All other .rfid files will use
  18. * the value specified in the Settings
  19. *
  20. * pause <ms>
  21. * Pauses the playback for 'ms' milliseconds.
  22. *
  23. * Blank lines, and comments (start with '#') are ignored. Whitespace is trimmed.
  24. *
  25. */
  26. void action_qpl_tx(void* context, const FuriString* action_path, FuriString* error) {
  27. App* app = context;
  28. // Save the current RFID and NFC Durations, in case the are changed during playback
  29. uint32_t orig_rfid_duration = app->settings.rfid_duration;
  30. uint32_t orig_nfc_duration = app->settings.nfc_duration;
  31. FuriString* buffer;
  32. buffer = furi_string_alloc();
  33. Stream* file = file_stream_alloc(app->storage);
  34. if(file_stream_open(file, furi_string_get_cstr(action_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
  35. while(stream_read_line(file, buffer)) {
  36. furi_string_trim(buffer); // remove '\n\r' line endings, cleanup spaces
  37. // FURI_LOG_I(TAG, "line: %s", furi_string_get_cstr(buffer));
  38. // Skip blank lines
  39. if(furi_string_size(buffer) == 0) {
  40. continue;
  41. }
  42. // Skip comments
  43. char first_char = furi_string_get_char(buffer, 0);
  44. if(first_char == '#') {
  45. continue;
  46. }
  47. // Check if buffer is a "command", and not just a filename
  48. // Commands will contain spaces
  49. bool processed_special_command = false;
  50. FuriString* args_tmp;
  51. args_tmp = furi_string_alloc();
  52. do {
  53. if(!args_read_string_and_trim(buffer, args_tmp)) {
  54. // No spaces found, buffer and args_tmp are now have same contents
  55. break;
  56. }
  57. // FURI_LOG_I(
  58. // TAG,
  59. // "args_temp: '%s', buffer: '%s'",
  60. // furi_string_get_cstr(args_tmp),
  61. // furi_string_get_cstr(buffer));
  62. // OK, there's a space, and args_tmp is the first token, buffer is the rest
  63. if(furi_string_cmpi_str(args_tmp, "pause") == 0) {
  64. processed_special_command = true;
  65. uint32_t pause_length = 0;
  66. if(sscanf(furi_string_get_cstr(buffer), "%lu", &pause_length) == 1) {
  67. FURI_LOG_I(TAG, "Pausing playlist for %lu ms", pause_length);
  68. furi_delay_ms(pause_length);
  69. } else {
  70. ACTION_SET_ERROR("Playlist: Invalid or missing pause time");
  71. }
  72. break;
  73. }
  74. // First token wasn't "pause", so maybe args_tmp is a .rfid filename followed
  75. // by a transmit duration in ms in buffer
  76. // Note: Not using path_extract_extension since it expects to find slashes in the
  77. // path, and thus won't work if we have a relative path file
  78. char ext[MAX_EXT_LEN + 1] = "";
  79. size_t dot = furi_string_search_rchar(args_tmp, '.');
  80. if(dot != FURI_STRING_FAILURE && furi_string_size(args_tmp) - dot <= MAX_EXT_LEN) {
  81. strlcpy(ext, &(furi_string_get_cstr(args_tmp))[dot], MAX_EXT_LEN);
  82. }
  83. // FURI_LOG_I(TAG, " - Found extension of %s", ext);
  84. if(!strcmp(ext, ".rfid")) {
  85. uint32_t rfid_duration = 0;
  86. // FURI_LOG_I(TAG, "RFID file with duration");
  87. if(sscanf(furi_string_get_cstr(buffer), "%lu", &rfid_duration) == 1) {
  88. FURI_LOG_I(TAG, "RFID duration = %lu", rfid_duration);
  89. app->settings.rfid_duration = rfid_duration;
  90. }
  91. } else if(!strcmp(ext, ".nfc")) {
  92. uint32_t nfc_duration = 0;
  93. if(sscanf(furi_string_get_cstr(buffer), "%lu", &nfc_duration) == 1) {
  94. FURI_LOG_I(TAG, "NFC duration = %lu", nfc_duration);
  95. app->settings.nfc_duration = nfc_duration;
  96. }
  97. }
  98. } while(false);
  99. furi_string_swap(buffer, args_tmp);
  100. furi_string_free(args_tmp);
  101. if(processed_special_command) {
  102. continue;
  103. }
  104. first_char = furi_string_get_char(buffer, 0);
  105. // Using relative paths? Prepend path of our playlist file
  106. if(first_char != '/') {
  107. FuriString* dirname;
  108. dirname = furi_string_alloc();
  109. path_extract_dirname(furi_string_get_cstr(action_path), dirname);
  110. furi_string_cat_printf(dirname, "/%s", furi_string_get_cstr(buffer));
  111. furi_string_swap(dirname, buffer);
  112. furi_string_free(dirname);
  113. }
  114. char ext[MAX_EXT_LEN + 1] = "";
  115. path_extract_extension(buffer, ext, MAX_EXT_LEN);
  116. if(!strcmp(ext, ".sub")) {
  117. action_subghz_tx(context, buffer, error);
  118. } else if(!strcmp(ext, ".rfid")) {
  119. action_rfid_tx(context, buffer, error);
  120. // Reset our default duration back - in case it was changed during playback
  121. app->settings.rfid_duration = orig_rfid_duration;
  122. } else if(!strcmp(ext, ".ir")) {
  123. action_ir_tx(context, buffer, error);
  124. } else if(!strcmp(ext, ".nfc")) {
  125. action_nfc_tx(context, buffer, error);
  126. // Reset our default duration back - in case it was changed during playback
  127. app->settings.nfc_duration = orig_nfc_duration;
  128. } else if(!strcmp(ext, ".qpl")) {
  129. ACTION_SET_ERROR("Playlist: Can't call playlist from playlist");
  130. } else {
  131. ACTION_SET_ERROR(
  132. "Playlist: Unknown file/command! %s", furi_string_get_cstr(buffer));
  133. }
  134. if(furi_string_size(error)) {
  135. // Abort playing the playlist - one of our actions failed
  136. break;
  137. }
  138. // Playlist action complete!
  139. // TODO: Do we need a small delay (say 25ms) between actions?
  140. // TODO: Should we blip the light a diff color to indicate that
  141. // we're done with one command and moving to the next?
  142. // furi_delay_ms(25);
  143. }
  144. } else {
  145. ACTION_SET_ERROR("Could not open playlist");
  146. }
  147. furi_string_free(buffer);
  148. file_stream_close(file);
  149. stream_free(file);
  150. }