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