action_qpl.c 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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. * Each line of the playlist file is one of:
  11. * <file_path>
  12. * Full SD card path, or relative path to action to be transmitted. Must be
  13. * one of the supported filetypes (.sub, .rfid, [.ir coming soon])
  14. * pause <ms> - NOT IMPLEMENTED
  15. * Pauses the playback for 'ms' milliseconds.
  16. *
  17. * Blank lines, and comments (start with '#') are ignored. Whitespace is trimmed.
  18. *
  19. * Not yet Implemented:
  20. * - For RFID files, if they have a space followed by a number after their name,
  21. * that number will be the duration of that RFID tx
  22. */
  23. void action_qpl_tx(void* context, FuriString* action_path, FuriString* error) {
  24. App* app = context;
  25. FuriString* buffer;
  26. buffer = furi_string_alloc();
  27. Stream* file = file_stream_alloc(app->storage);
  28. if(file_stream_open(file, furi_string_get_cstr(action_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
  29. while(stream_read_line(file, buffer)) {
  30. furi_string_trim(buffer); // remove '\n\r' line endings, cleanup spaces
  31. FURI_LOG_I(TAG, "line: %s", furi_string_get_cstr(buffer));
  32. // Skip blank lines
  33. if(furi_string_size(buffer) == 0) {
  34. continue;
  35. }
  36. // Skip comments
  37. char first_char = furi_string_get_char(buffer, 0);
  38. if(first_char == '#') {
  39. continue;
  40. }
  41. // Check if buffer is a "command", and not just a filename
  42. // Commands will contain spaces
  43. bool processed_special_command = false;
  44. FuriString* args_tmp;
  45. args_tmp = furi_string_alloc();
  46. do {
  47. if(!args_read_string_and_trim(buffer, args_tmp)) {
  48. // No spaces found, buffer and args_tmp are now have same contents
  49. break;
  50. }
  51. // FURI_LOG_I(
  52. // TAG,
  53. // "args_temp: '%s', buffer: '%s'",
  54. // furi_string_get_cstr(args_tmp),
  55. // furi_string_get_cstr(buffer));
  56. // OK, there's a space, and args_tmp is the first token, buffer is the rest
  57. if(furi_string_cmpi_str(args_tmp, "pause") == 0) {
  58. processed_special_command = true;
  59. uint32_t pause_length = 0;
  60. if(sscanf(furi_string_get_cstr(buffer), "%lu", &pause_length) == 1) {
  61. FURI_LOG_I(TAG, "Pausing playlist for %lu ms", pause_length);
  62. furi_delay_ms(pause_length);
  63. } else {
  64. ACTION_SET_ERROR("Playlist: Invalid or missing pause time");
  65. }
  66. break;
  67. }
  68. // FURI_LOG_I(TAG, "Still checking for commands...");
  69. // FURI_LOG_I(
  70. // TAG,
  71. // "args_temp: '%s', buffer: '%s'",
  72. // furi_string_get_cstr(args_tmp),
  73. // furi_string_get_cstr(buffer));
  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. uint32_t rfid_duration = 0;
  85. if(!strcmp(ext, ".rfid")) {
  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. // TODO: Need to get the duration to the action_rfid_tx command...
  90. }
  91. }
  92. } while(false);
  93. furi_string_swap(buffer, args_tmp);
  94. furi_string_free(args_tmp);
  95. if(processed_special_command) {
  96. continue;
  97. }
  98. first_char = furi_string_get_char(buffer, 0);
  99. // Using relative paths? Prepend path of our playlist file
  100. if(first_char != '/') {
  101. FuriString* dirname;
  102. dirname = furi_string_alloc();
  103. path_extract_dirname(furi_string_get_cstr(action_path), dirname);
  104. furi_string_cat_printf(dirname, "/%s", furi_string_get_cstr(buffer));
  105. furi_string_swap(dirname, buffer);
  106. furi_string_free(dirname);
  107. }
  108. char ext[MAX_EXT_LEN + 1] = "";
  109. path_extract_extension(buffer, ext, MAX_EXT_LEN);
  110. if(!strcmp(ext, ".sub")) {
  111. action_subghz_tx(context, buffer, error);
  112. } else if(!strcmp(ext, ".ir")) {
  113. action_ir_tx(context, buffer, error);
  114. } else if(!strcmp(ext, ".rfid")) {
  115. action_rfid_tx(context, buffer, error);
  116. } else if(!strcmp(ext, ".qpl")) {
  117. ACTION_SET_ERROR("Playlist: Can't call playlist from playlist");
  118. } else {
  119. ACTION_SET_ERROR(
  120. "Playlist: Unknown file/command! %s", furi_string_get_cstr(buffer));
  121. }
  122. if(furi_string_size(error)) {
  123. // Abort playing the playlist - one of our actions failed
  124. break;
  125. }
  126. // Playlist action complete!
  127. // TODO: Do we need a small delay (say 25ms) between actions?
  128. // TODO: Should we blip the light a diff color to indicate that
  129. // we're done with one command and moving to the next?
  130. // furi_delay_ms(25);
  131. }
  132. } else {
  133. ACTION_SET_ERROR("Could not open playlist");
  134. }
  135. furi_string_free(buffer);
  136. file_stream_close(file);
  137. stream_free(file);
  138. }