action_qpl.c 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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 Duration, in case it is changed during playback
  29. uint32_t orig_rfid_duration = app->settings.rfid_duration;
  30. FuriString* buffer;
  31. buffer = furi_string_alloc();
  32. Stream* file = file_stream_alloc(app->storage);
  33. if(file_stream_open(file, furi_string_get_cstr(action_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
  34. while(stream_read_line(file, buffer)) {
  35. furi_string_trim(buffer); // remove '\n\r' line endings, cleanup spaces
  36. // FURI_LOG_I(TAG, "line: %s", furi_string_get_cstr(buffer));
  37. // Skip blank lines
  38. if(furi_string_size(buffer) == 0) {
  39. continue;
  40. }
  41. // Skip comments
  42. char first_char = furi_string_get_char(buffer, 0);
  43. if(first_char == '#') {
  44. continue;
  45. }
  46. // Check if buffer is a "command", and not just a filename
  47. // Commands will contain spaces
  48. bool processed_special_command = false;
  49. FuriString* args_tmp;
  50. args_tmp = furi_string_alloc();
  51. do {
  52. if(!args_read_string_and_trim(buffer, args_tmp)) {
  53. // No spaces found, buffer and args_tmp are now have same contents
  54. break;
  55. }
  56. // FURI_LOG_I(
  57. // TAG,
  58. // "args_temp: '%s', buffer: '%s'",
  59. // furi_string_get_cstr(args_tmp),
  60. // furi_string_get_cstr(buffer));
  61. // OK, there's a space, and args_tmp is the first token, buffer is the rest
  62. if(furi_string_cmpi_str(args_tmp, "pause") == 0) {
  63. processed_special_command = true;
  64. uint32_t pause_length = 0;
  65. if(sscanf(furi_string_get_cstr(buffer), "%lu", &pause_length) == 1) {
  66. FURI_LOG_I(TAG, "Pausing playlist for %lu ms", pause_length);
  67. furi_delay_ms(pause_length);
  68. } else {
  69. ACTION_SET_ERROR("Playlist: Invalid or missing pause time");
  70. }
  71. break;
  72. }
  73. // FURI_LOG_I(TAG, "Still checking for commands...");
  74. // FURI_LOG_I(
  75. // TAG,
  76. // "args_temp: '%s', buffer: '%s'",
  77. // furi_string_get_cstr(args_tmp),
  78. // furi_string_get_cstr(buffer));
  79. // First token wasn't "pause", so maybe args_tmp is a .rfid filename followed
  80. // by a transmit duration in ms in buffer
  81. // Note: Not using path_extract_extension since it expects to find slashes in the
  82. // path, and thus won't work if we have a relative path file
  83. char ext[MAX_EXT_LEN + 1] = "";
  84. size_t dot = furi_string_search_rchar(args_tmp, '.');
  85. if(dot != FURI_STRING_FAILURE && furi_string_size(args_tmp) - dot <= MAX_EXT_LEN) {
  86. strlcpy(ext, &(furi_string_get_cstr(args_tmp))[dot], MAX_EXT_LEN);
  87. }
  88. // FURI_LOG_I(TAG, " - Found extension of %s", ext);
  89. uint32_t rfid_duration = 0;
  90. if(!strcmp(ext, ".rfid")) {
  91. // FURI_LOG_I(TAG, "RFID file with duration");
  92. if(sscanf(furi_string_get_cstr(buffer), "%lu", &rfid_duration) == 1) {
  93. FURI_LOG_I(TAG, "RFID duration = %lu", rfid_duration);
  94. app->settings.rfid_duration = rfid_duration;
  95. }
  96. }
  97. } while(false);
  98. furi_string_swap(buffer, args_tmp);
  99. furi_string_free(args_tmp);
  100. if(processed_special_command) {
  101. continue;
  102. }
  103. first_char = furi_string_get_char(buffer, 0);
  104. // Using relative paths? Prepend path of our playlist file
  105. if(first_char != '/') {
  106. FuriString* dirname;
  107. dirname = furi_string_alloc();
  108. path_extract_dirname(furi_string_get_cstr(action_path), dirname);
  109. furi_string_cat_printf(dirname, "/%s", furi_string_get_cstr(buffer));
  110. furi_string_swap(dirname, buffer);
  111. furi_string_free(dirname);
  112. }
  113. char ext[MAX_EXT_LEN + 1] = "";
  114. path_extract_extension(buffer, ext, MAX_EXT_LEN);
  115. if(!strcmp(ext, ".sub")) {
  116. action_subghz_tx(context, buffer, error);
  117. } else if(!strcmp(ext, ".rfid")) {
  118. action_rfid_tx(context, buffer, error);
  119. // Reset our default duration back - in case it was changed during playback
  120. app->settings.rfid_duration = orig_rfid_duration;
  121. } else if(!strcmp(ext, ".ir")) {
  122. action_ir_tx(context, buffer, error);
  123. } else if(!strcmp(ext, ".qpl")) {
  124. ACTION_SET_ERROR("Playlist: Can't call playlist from playlist");
  125. } else {
  126. ACTION_SET_ERROR(
  127. "Playlist: Unknown file/command! %s", furi_string_get_cstr(buffer));
  128. }
  129. if(furi_string_size(error)) {
  130. // Abort playing the playlist - one of our actions failed
  131. break;
  132. }
  133. // Playlist action complete!
  134. // TODO: Do we need a small delay (say 25ms) between actions?
  135. // TODO: Should we blip the light a diff color to indicate that
  136. // we're done with one command and moving to the next?
  137. // furi_delay_ms(25);
  138. }
  139. } else {
  140. ACTION_SET_ERROR("Could not open playlist");
  141. }
  142. furi_string_free(buffer);
  143. file_stream_close(file);
  144. stream_free(file);
  145. }