Commit | Line | Data |
---|---|---|
663d7943 L |
1 | /* |
2 | This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl). | |
3 | Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. | |
4 | Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
5 | You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>. | |
6 | */ | |
7 | ||
9ef9c12a | 8 | #include "Player.h" |
663d7943 L |
9 | |
10 | #include "libs/Kernel.h" | |
663d7943 L |
11 | #include "libs/nuts_bolts.h" |
12 | #include "libs/utils.h" | |
61134a65 | 13 | #include "SerialConsole.h" |
663d7943 | 14 | #include "libs/SerialMessage.h" |
61134a65 | 15 | #include "libs/StreamOutputPool.h" |
663d7943 | 16 | #include "libs/StreamOutput.h" |
61134a65 JM |
17 | #include "Gcode.h" |
18 | #include "checksumm.h" | |
19 | #include "Pauser.h" | |
20 | #include "Config.h" | |
8d54c34c | 21 | #include "ConfigValue.h" |
4fba6676 | 22 | #include "SDFAT.h" |
61134a65 | 23 | |
663d7943 | 24 | #include "modules/robot/Conveyor.h" |
172d42d9 | 25 | #include "DirHandle.h" |
35089dc7 JM |
26 | #include "PublicDataRequest.h" |
27 | #include "PlayerPublicAccess.h" | |
663d7943 | 28 | |
75f4581c JM |
29 | #define on_boot_gcode_checksum CHECKSUM("on_boot_gcode") |
30 | #define on_boot_gcode_enable_checksum CHECKSUM("on_boot_gcode_enable") | |
31 | ||
4fba6676 | 32 | extern SDFAT mounter; |
33 | ||
36aa8b0b JM |
34 | void Player::on_module_loaded() |
35 | { | |
663d7943 | 36 | this->playing_file = false; |
36aa8b0b | 37 | this->current_file_handler = NULL; |
addfb453 | 38 | this->booted = false; |
663d7943 L |
39 | this->register_for_event(ON_CONSOLE_LINE_RECEIVED); |
40 | this->register_for_event(ON_MAIN_LOOP); | |
63888663 | 41 | this->register_for_event(ON_SECOND_TICK); |
58d6d841 JM |
42 | this->register_for_event(ON_GET_PUBLIC_DATA); |
43 | this->register_for_event(ON_SET_PUBLIC_DATA); | |
c4e56997 | 44 | this->register_for_event(ON_GCODE_RECEIVED); |
76217df5 | 45 | this->register_for_event(ON_HALT); |
8fef244a | 46 | |
314ab8f7 MM |
47 | this->on_boot_gcode = THEKERNEL->config->value(on_boot_gcode_checksum)->by_default("/sd/on_boot.gcode")->as_string(); |
48 | this->on_boot_gcode_enable = THEKERNEL->config->value(on_boot_gcode_enable_checksum)->by_default(true)->as_bool(); | |
36aa8b0b JM |
49 | this->elapsed_secs = 0; |
50 | this->reply_stream = NULL; | |
4c09283b | 51 | this->halted= false; |
63888663 JM |
52 | } |
53 | ||
76217df5 JM |
54 | void Player::on_halt(void *) |
55 | { | |
4c09283b | 56 | halted= true; |
76217df5 JM |
57 | } |
58 | ||
36aa8b0b JM |
59 | void Player::on_second_tick(void *) |
60 | { | |
61 | if (!THEKERNEL->pauser->paused()) this->elapsed_secs++; | |
663d7943 L |
62 | } |
63 | ||
428d1181 JM |
64 | // extract any options found on line, terminates args at the space before the first option (-v) |
65 | // eg this is a file.gcode -v | |
66 | // will return -v and set args to this is a file.gcode | |
67 | string Player::extract_options(string& args) | |
68 | { | |
69 | string opts; | |
70 | size_t pos= args.find(" -"); | |
71 | if(pos != string::npos) { | |
72 | opts= args.substr(pos); | |
73 | args= args.substr(0, pos); | |
74 | } | |
75 | ||
76 | return opts; | |
77 | } | |
78 | ||
36aa8b0b JM |
79 | void Player::on_gcode_received(void *argument) |
80 | { | |
81 | Gcode *gcode = static_cast<Gcode *>(argument); | |
8090190d | 82 | string args = get_arguments(gcode->get_command()); |
c4e56997 | 83 | if (gcode->has_m) { |
6d4d88bc CG |
84 | if (gcode->m == 21) { // Dummy code; makes Octoprint happy -- supposed to initialize SD card |
85 | gcode->mark_as_taken(); | |
ca8a9d29 | 86 | mounter.remount(); |
6d4d88bc | 87 | gcode->stream->printf("SD card ok\r\n"); |
4c8afa75 | 88 | |
36aa8b0b | 89 | } else if (gcode->m == 23) { // select file |
c4e56997 | 90 | gcode->mark_as_taken(); |
428d1181 | 91 | this->filename = "/sd/" + args; // filename is whatever is in args |
c4e56997 JM |
92 | this->current_stream = &(StreamOutput::NullStream); |
93 | ||
94 | if(this->current_file_handler != NULL) { | |
95 | this->playing_file = false; | |
96 | fclose(this->current_file_handler); | |
97 | } | |
e2cd3423 | 98 | this->current_file_handler = fopen( this->filename.c_str(), "r"); |
fbd92b6a | 99 | |
36aa8b0b | 100 | if(this->current_file_handler == NULL) { |
e2cd3423 | 101 | gcode->stream->printf("file.open failed: %s\r\n", this->filename.c_str()); |
1e6e89ee JM |
102 | return; |
103 | ||
36aa8b0b | 104 | } else { |
1e6e89ee JM |
105 | // get size of file |
106 | int result = fseek(this->current_file_handler, 0, SEEK_END); | |
36aa8b0b JM |
107 | if (0 != result) { |
108 | this->file_size = 0; | |
109 | } else { | |
110 | this->file_size = ftell(this->current_file_handler); | |
111 | fseek(this->current_file_handler, 0, SEEK_SET); | |
1e6e89ee JM |
112 | } |
113 | gcode->stream->printf("File opened:%s Size:%ld\r\n", this->filename.c_str(), this->file_size); | |
fbd92b6a | 114 | gcode->stream->printf("File selected\r\n"); |
c4e56997 JM |
115 | } |
116 | ||
1e6e89ee | 117 | |
36aa8b0b JM |
118 | this->played_cnt = 0; |
119 | this->elapsed_secs = 0; | |
fbd92b6a | 120 | |
36aa8b0b | 121 | } else if (gcode->m == 24) { // start print |
c4e56997 JM |
122 | gcode->mark_as_taken(); |
123 | if (this->current_file_handler != NULL) { | |
124 | this->playing_file = true; | |
d4ee6ee2 JM |
125 | // this would be a problem if the stream goes away before the file has finished, |
126 | // so we attach it to the kernel stream, however network connections from pronterface | |
127 | // do not connect to the kernel streams so won't see this FIXME | |
36aa8b0b | 128 | this->reply_stream = THEKERNEL->streams; |
c4e56997 JM |
129 | } |
130 | ||
36aa8b0b | 131 | } else if (gcode->m == 25) { // pause print |
c4e56997 JM |
132 | gcode->mark_as_taken(); |
133 | this->playing_file = false; | |
134 | ||
36aa8b0b | 135 | } else if (gcode->m == 26) { // Reset print. Slightly different than M26 in Marlin and the rest |
4c60a46b | 136 | gcode->mark_as_taken(); |
36aa8b0b JM |
137 | if(this->current_file_handler != NULL) { |
138 | string currentfn = this->filename.c_str(); | |
139 | unsigned long old_size = this->file_size; | |
1e6e89ee | 140 | |
4c60a46b CG |
141 | // abort the print |
142 | abort_command("", gcode->stream); | |
4c8afa75 | 143 | |
1e6e89ee JM |
144 | if(!currentfn.empty()) { |
145 | // reload the last file opened | |
146 | this->current_file_handler = fopen(currentfn.c_str() , "r"); | |
4c8afa75 | 147 | |
36aa8b0b | 148 | if(this->current_file_handler == NULL) { |
1e6e89ee | 149 | gcode->stream->printf("file.open failed: %s\r\n", currentfn.c_str()); |
36aa8b0b JM |
150 | } else { |
151 | this->filename = currentfn; | |
152 | this->file_size = old_size; | |
1e6e89ee | 153 | this->current_stream = &(StreamOutput::NullStream); |
4c60a46b CG |
154 | } |
155 | } | |
1e6e89ee | 156 | |
36aa8b0b | 157 | } else { |
4c60a46b CG |
158 | gcode->stream->printf("No file loaded\r\n"); |
159 | } | |
4c8afa75 | 160 | |
36aa8b0b | 161 | } else if (gcode->m == 27) { // report print progress, in format used by Marlin |
12ce77b7 | 162 | gcode->mark_as_taken(); |
43d26cb1 | 163 | progress_command("-b", gcode->stream); |
6d4d88bc | 164 | |
36aa8b0b | 165 | } else if (gcode->m == 32) { // select file and start print |
3661ec47 | 166 | gcode->mark_as_taken(); |
6d4d88bc | 167 | // Get filename |
428d1181 | 168 | this->filename = "/sd/" + args; // filename is whatever is in args including spaces |
6d4d88bc CG |
169 | this->current_stream = &(StreamOutput::NullStream); |
170 | ||
171 | if(this->current_file_handler != NULL) { | |
172 | this->playing_file = false; | |
173 | fclose(this->current_file_handler); | |
174 | } | |
175 | ||
176 | this->current_file_handler = fopen( this->filename.c_str(), "r"); | |
36aa8b0b | 177 | if(this->current_file_handler == NULL) { |
6d4d88bc | 178 | gcode->stream->printf("file.open failed: %s\r\n", this->filename.c_str()); |
36aa8b0b | 179 | } else { |
6d4d88bc CG |
180 | this->playing_file = true; |
181 | } | |
b34495c6 JM |
182 | |
183 | }else if (gcode->m == 999) { | |
184 | halted= false; | |
c4e56997 JM |
185 | } |
186 | } | |
187 | } | |
188 | ||
663d7943 | 189 | // When a new line is received, check if it is a command, and if it is, act upon it |
36aa8b0b JM |
190 | void Player::on_console_line_received( void *argument ) |
191 | { | |
b34495c6 JM |
192 | if(halted) return; // if in halted state ignore any commands |
193 | ||
36aa8b0b | 194 | SerialMessage new_message = *static_cast<SerialMessage *>(argument); |
7f613782 | 195 | |
7e81f138 JM |
196 | // ignore comments and blank lines and if this is a G code then also ignore it |
197 | char first_char = new_message.message[0]; | |
198 | if(strchr(";( \n\rGMTN", first_char) != NULL) return; | |
7f613782 | 199 | |
663d7943 | 200 | string possible_command = new_message.message; |
7e81f138 | 201 | string cmd = shift_parameter(possible_command); |
663d7943 L |
202 | |
203 | //new_message.stream->printf("Received %s\r\n", possible_command.c_str()); | |
204 | ||
663d7943 | 205 | // Act depending on command |
7e81f138 JM |
206 | if (cmd == "play"){ |
207 | this->play_command( possible_command, new_message.stream ); | |
208 | }else if (cmd == "progress"){ | |
209 | this->progress_command( possible_command, new_message.stream ); | |
210 | }else if (cmd == "abort") | |
211 | this->abort_command( possible_command, new_message.stream ); | |
663d7943 L |
212 | } |
213 | ||
214 | // Play a gcode file by considering each line as if it was received on the serial console | |
36aa8b0b JM |
215 | void Player::play_command( string parameters, StreamOutput *stream ) |
216 | { | |
428d1181 JM |
217 | // extract any options from the line and terminate the line there |
218 | string options= extract_options(parameters); | |
219 | // Get filename which is the entire parameter line upto any options found or entire line | |
220 | this->filename = absolute_from_relative(parameters); | |
663d7943 | 221 | |
36aa8b0b JM |
222 | if(this->playing_file) { |
223 | stream->printf("Currently printing, abort print first\r\n"); | |
224 | return; | |
225 | } | |
226 | ||
227 | if(this->current_file_handler != NULL) { // must have been a paused print | |
228 | fclose(this->current_file_handler); | |
229 | } | |
230 | ||
e2cd3423 | 231 | this->current_file_handler = fopen( this->filename.c_str(), "r"); |
36aa8b0b | 232 | if(this->current_file_handler == NULL) { |
e2cd3423 | 233 | stream->printf("File not found: %s\r\n", this->filename.c_str()); |
663d7943 L |
234 | return; |
235 | } | |
fdbea18b | 236 | |
e2cd3423 | 237 | stream->printf("Playing %s\r\n", this->filename.c_str()); |
8fef244a | 238 | |
663d7943 | 239 | this->playing_file = true; |
fdbea18b | 240 | |
4eb0e279 | 241 | // Output to the current stream if we were passed the -v ( verbose ) option |
36aa8b0b | 242 | if( options.find_first_of("Vv") == string::npos ) { |
d0ef6382 | 243 | this->current_stream = &(StreamOutput::NullStream); |
36aa8b0b | 244 | } else { |
d4ee6ee2 JM |
245 | // we send to the kernels stream as it cannot go away |
246 | this->current_stream = THEKERNEL->streams; | |
fdbea18b AW |
247 | } |
248 | ||
249 | // get size of file | |
250 | int result = fseek(this->current_file_handler, 0, SEEK_END); | |
36aa8b0b JM |
251 | if (0 != result) { |
252 | stream->printf("WARNING - Could not get file size\r\n"); | |
253 | file_size = 0; | |
254 | } else { | |
255 | file_size = ftell(this->current_file_handler); | |
256 | fseek(this->current_file_handler, 0, SEEK_SET); | |
257 | stream->printf(" File size %ld\r\n", file_size); | |
fdbea18b | 258 | } |
36aa8b0b JM |
259 | this->played_cnt = 0; |
260 | this->elapsed_secs = 0; | |
addfb453 L |
261 | } |
262 | ||
36aa8b0b JM |
263 | void Player::progress_command( string parameters, StreamOutput *stream ) |
264 | { | |
43d26cb1 CG |
265 | |
266 | // get options | |
36aa8b0b | 267 | string options = shift_parameter( parameters ); |
09351052 | 268 | bool sdprinting= options.find_first_of("Bb") != string::npos; |
43d26cb1 | 269 | |
36aa8b0b | 270 | if(!playing_file && current_file_handler != NULL) { |
09351052 JM |
271 | if(sdprinting) |
272 | stream->printf("SD printing byte %lu/%lu\r\n", played_cnt, file_size); | |
273 | else | |
274 | stream->printf("SD print is paused at %lu/%lu\r\n", played_cnt, file_size); | |
36aa8b0b JM |
275 | return; |
276 | ||
277 | } else if(!playing_file) { | |
b41aedba JM |
278 | stream->printf("Not currently playing\r\n"); |
279 | return; | |
280 | } | |
281 | ||
282 | if(file_size > 0) { | |
36aa8b0b | 283 | unsigned long est = 0; |
0a3f2832 | 284 | if(this->elapsed_secs > 10) { |
36aa8b0b | 285 | unsigned long bytespersec = played_cnt / this->elapsed_secs; |
b41aedba | 286 | if(bytespersec > 0) |
36aa8b0b | 287 | est = (file_size - played_cnt) / bytespersec; |
b41aedba | 288 | } |
8fef244a | 289 | |
36aa8b0b | 290 | unsigned int pcnt = (file_size - (file_size - played_cnt)) * 100 / file_size; |
43d26cb1 | 291 | // If -b or -B is passed, report in the format used by Marlin and the others. |
09351052 | 292 | if (!sdprinting) { |
104293d2 | 293 | stream->printf("%u %% complete, elapsed time: %lu s", pcnt, this->elapsed_secs); |
36aa8b0b | 294 | if(est > 0) { |
104293d2 | 295 | stream->printf(", est time: %lu s", est); |
43d26cb1 CG |
296 | } |
297 | stream->printf("\r\n"); | |
36aa8b0b | 298 | } else { |
104293d2 | 299 | stream->printf("SD printing byte %lu/%lu\r\n", played_cnt, file_size); |
0a3f2832 | 300 | } |
8fef244a | 301 | |
36aa8b0b | 302 | } else { |
b41aedba JM |
303 | stream->printf("File size is unknown\r\n"); |
304 | } | |
addfb453 L |
305 | } |
306 | ||
36aa8b0b JM |
307 | void Player::abort_command( string parameters, StreamOutput *stream ) |
308 | { | |
309 | if(!playing_file && current_file_handler == NULL) { | |
b41aedba JM |
310 | stream->printf("Not currently playing\r\n"); |
311 | return; | |
312 | } | |
313 | playing_file = false; | |
36aa8b0b JM |
314 | played_cnt = 0; |
315 | file_size = 0; | |
316 | this->filename = ""; | |
317 | this->current_stream = NULL; | |
b41aedba | 318 | fclose(current_file_handler); |
36aa8b0b | 319 | current_file_handler = NULL; |
4c09283b JM |
320 | // clear out the block queue |
321 | THEKERNEL->conveyor->flush_queue(); | |
36aa8b0b | 322 | stream->printf("Aborted playing or paused file\r\n"); |
addfb453 L |
323 | } |
324 | ||
36aa8b0b JM |
325 | void Player::on_main_loop(void *argument) |
326 | { | |
38776a0b | 327 | if( !this->booted ) { |
33e4cc02 | 328 | this->booted = true; |
36aa8b0b | 329 | if( this->on_boot_gcode_enable ) { |
314ab8f7 | 330 | this->play_command(this->on_boot_gcode, THEKERNEL->serial); |
36aa8b0b | 331 | } else { |
314ab8f7 | 332 | //THEKERNEL->serial->printf("On boot gcode disabled! skipping...\n"); |
38776a0b | 333 | } |
addfb453 L |
334 | } |
335 | ||
36aa8b0b | 336 | if( this->playing_file ) { |
4c09283b | 337 | if(halted) { |
4c09283b JM |
338 | abort_command("", &(StreamOutput::NullStream)); |
339 | return; | |
340 | } | |
341 | ||
9ef9c12a | 342 | char buf[130]; // lines upto 128 characters are allowed, anything longer is discarded |
36aa8b0b | 343 | bool discard = false; |
9ef9c12a JM |
344 | |
345 | while(fgets(buf, sizeof(buf), this->current_file_handler) != NULL) { | |
36aa8b0b | 346 | int len = strlen(buf); |
9ef9c12a | 347 | if(len == 0) continue; // empty line? should not be possible |
7a1b71b5 | 348 | if(buf[len - 1] == '\n' || feof(this->current_file_handler)) { |
9ef9c12a | 349 | if(discard) { // we are discarding a long line |
36aa8b0b | 350 | discard = false; |
9ef9c12a | 351 | continue; |
63ada22a | 352 | } |
9ef9c12a JM |
353 | if(len == 1) continue; // empty line |
354 | ||
355 | this->current_stream->printf("%s", buf); | |
addfb453 | 356 | struct SerialMessage message; |
9ef9c12a | 357 | message.message = buf; |
681a62d7 | 358 | message.stream = this->current_stream; |
36aa8b0b JM |
359 | |
360 | // waits for the queue to have enough room | |
314ab8f7 | 361 | THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message); |
9ef9c12a JM |
362 | played_cnt += len; |
363 | return; // we feed one line per main loop | |
63ada22a | 364 | |
36aa8b0b | 365 | } else { |
9ef9c12a JM |
366 | // discard long line |
367 | this->current_stream->printf("Warning: Discarded long line\n"); | |
36aa8b0b | 368 | discard = true; |
addfb453 | 369 | } |
c4e56997 JM |
370 | } |
371 | ||
b41aedba | 372 | this->playing_file = false; |
36aa8b0b JM |
373 | this->filename = ""; |
374 | played_cnt = 0; | |
375 | file_size = 0; | |
addfb453 | 376 | fclose(this->current_file_handler); |
36aa8b0b JM |
377 | current_file_handler = NULL; |
378 | this->current_stream = NULL; | |
c4e56997 JM |
379 | |
380 | if(this->reply_stream != NULL) { | |
381 | // if we were printing from an M command from pronterface we need to send this back | |
382 | this->reply_stream->printf("Done printing file\r\n"); | |
36aa8b0b | 383 | this->reply_stream = NULL; |
c4e56997 | 384 | } |
663d7943 L |
385 | } |
386 | } | |
387 | ||
36aa8b0b JM |
388 | void Player::on_get_public_data(void *argument) |
389 | { | |
390 | PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument); | |
58d6d841 JM |
391 | |
392 | if(!pdr->starts_with(player_checksum)) return; | |
393 | ||
394 | if(pdr->second_element_is(is_playing_checksum)) { | |
395 | static bool bool_data; | |
36aa8b0b | 396 | bool_data = this->playing_file; |
58d6d841 JM |
397 | pdr->set_data_ptr(&bool_data); |
398 | pdr->set_taken(); | |
c4e56997 | 399 | |
36aa8b0b | 400 | } else if(pdr->second_element_is(get_progress_checksum)) { |
58d6d841 JM |
401 | static struct pad_progress p; |
402 | if(file_size > 0 && playing_file) { | |
36aa8b0b JM |
403 | p.elapsed_secs = this->elapsed_secs; |
404 | p.percent_complete = (this->file_size - (this->file_size - this->played_cnt)) * 100 / this->file_size; | |
405 | p.filename = this->filename; | |
58d6d841 JM |
406 | pdr->set_data_ptr(&p); |
407 | pdr->set_taken(); | |
408 | } | |
409 | } | |
35089dc7 JM |
410 | } |
411 | ||
36aa8b0b JM |
412 | void Player::on_set_public_data(void *argument) |
413 | { | |
414 | PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument); | |
58d6d841 JM |
415 | |
416 | if(!pdr->starts_with(player_checksum)) return; | |
417 | ||
418 | if(pdr->second_element_is(abort_play_checksum)) { | |
4c8afa75 JM |
419 | abort_command("", &(StreamOutput::NullStream)); |
420 | pdr->set_taken(); | |
58d6d841 | 421 | } |
35089dc7 | 422 | } |