temperaturecontrol: allow setting background tool without activating
[clinton/Smoothieware.git] / src / modules / utils / player / Player.cpp
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
8 #include "Player.h"
9
10 #include "libs/Kernel.h"
11 #include "Robot.h"
12 #include "libs/nuts_bolts.h"
13 #include "libs/utils.h"
14 #include "SerialConsole.h"
15 #include "libs/SerialMessage.h"
16 #include "libs/StreamOutputPool.h"
17 #include "libs/StreamOutput.h"
18 #include "Gcode.h"
19 #include "checksumm.h"
20 #include "Config.h"
21 #include "ConfigValue.h"
22 #include "SDFAT.h"
23
24 #include "modules/robot/Conveyor.h"
25 #include "DirHandle.h"
26 #include "PublicDataRequest.h"
27 #include "PublicData.h"
28 #include "PlayerPublicAccess.h"
29 #include "TemperatureControlPublicAccess.h"
30 #include "TemperatureControlPool.h"
31 #include "ExtruderPublicAccess.h"
32
33 #include <cstddef>
34 #include <cmath>
35 #include <algorithm>
36
37 #include "mbed.h"
38
39 #define on_boot_gcode_checksum CHECKSUM("on_boot_gcode")
40 #define on_boot_gcode_enable_checksum CHECKSUM("on_boot_gcode_enable")
41 #define after_suspend_gcode_checksum CHECKSUM("after_suspend_gcode")
42 #define before_resume_gcode_checksum CHECKSUM("before_resume_gcode")
43 #define leave_heaters_on_suspend_checksum CHECKSUM("leave_heaters_on_suspend")
44
45 extern SDFAT mounter;
46
47 Player::Player()
48 {
49 this->playing_file = false;
50 this->current_file_handler = nullptr;
51 this->booted = false;
52 this->elapsed_secs = 0;
53 this->reply_stream = nullptr;
54 this->suspended= false;
55 this->suspend_loops= 0;
56 }
57
58 void Player::on_module_loaded()
59 {
60 this->register_for_event(ON_CONSOLE_LINE_RECEIVED);
61 this->register_for_event(ON_MAIN_LOOP);
62 this->register_for_event(ON_SECOND_TICK);
63 this->register_for_event(ON_GET_PUBLIC_DATA);
64 this->register_for_event(ON_SET_PUBLIC_DATA);
65 this->register_for_event(ON_GCODE_RECEIVED);
66 this->register_for_event(ON_HALT);
67
68 this->on_boot_gcode = THEKERNEL->config->value(on_boot_gcode_checksum)->by_default("/sd/on_boot.gcode")->as_string();
69 this->on_boot_gcode_enable = THEKERNEL->config->value(on_boot_gcode_enable_checksum)->by_default(true)->as_bool();
70
71 this->after_suspend_gcode = THEKERNEL->config->value(after_suspend_gcode_checksum)->by_default("")->as_string();
72 this->before_resume_gcode = THEKERNEL->config->value(before_resume_gcode_checksum)->by_default("")->as_string();
73 std::replace( this->after_suspend_gcode.begin(), this->after_suspend_gcode.end(), '_', ' '); // replace _ with space
74 std::replace( this->before_resume_gcode.begin(), this->before_resume_gcode.end(), '_', ' '); // replace _ with space
75 this->leave_heaters_on = THEKERNEL->config->value(leave_heaters_on_suspend_checksum)->by_default(false)->as_bool();
76 }
77
78 void Player::on_halt(void* argument)
79 {
80 if(argument == nullptr && this->playing_file ) {
81 abort_command("1", &(StreamOutput::NullStream));
82 }
83
84 if(argument == nullptr && this->suspended) {
85 // clean up from suspend
86 this->suspended= false;
87 THEROBOT->pop_state();
88 this->saved_temperatures.clear();
89 this->was_playing_file= false;
90 this->suspend_loops= 0;
91 THEKERNEL->streams->printf("// Suspend cleared\n");
92 }
93 }
94
95 void Player::on_second_tick(void *)
96 {
97 if(this->playing_file) this->elapsed_secs++;
98 }
99
100 // extract any options found on line, terminates args at the space before the first option (-v)
101 // eg this is a file.gcode -v
102 // will return -v and set args to this is a file.gcode
103 string Player::extract_options(string& args)
104 {
105 string opts;
106 size_t pos= args.find(" -");
107 if(pos != string::npos) {
108 opts= args.substr(pos);
109 args= args.substr(0, pos);
110 }
111
112 return opts;
113 }
114
115 void Player::on_gcode_received(void *argument)
116 {
117 Gcode *gcode = static_cast<Gcode *>(argument);
118 string args = get_arguments(gcode->get_command());
119 if (gcode->has_m) {
120 if (gcode->m == 21) { // Dummy code; makes Octoprint happy -- supposed to initialize SD card
121 mounter.remount();
122 gcode->stream->printf("SD card ok\r\n");
123
124 } else if (gcode->m == 23) { // select file
125 this->filename = "/sd/" + args; // filename is whatever is in args
126 this->current_stream = nullptr;
127
128 if(this->current_file_handler != NULL) {
129 this->playing_file = false;
130 fclose(this->current_file_handler);
131 }
132 this->current_file_handler = fopen( this->filename.c_str(), "r");
133
134 if(this->current_file_handler == NULL) {
135 gcode->stream->printf("file.open failed: %s\r\n", this->filename.c_str());
136 return;
137
138 } else {
139 // get size of file
140 int result = fseek(this->current_file_handler, 0, SEEK_END);
141 if (0 != result) {
142 this->file_size = 0;
143 } else {
144 this->file_size = ftell(this->current_file_handler);
145 fseek(this->current_file_handler, 0, SEEK_SET);
146 }
147 gcode->stream->printf("File opened:%s Size:%ld\r\n", this->filename.c_str(), this->file_size);
148 gcode->stream->printf("File selected\r\n");
149 }
150
151
152 this->played_cnt = 0;
153 this->elapsed_secs = 0;
154
155 } else if (gcode->m == 24) { // start print
156 if (this->current_file_handler != NULL) {
157 this->playing_file = true;
158 // this would be a problem if the stream goes away before the file has finished,
159 // so we attach it to the kernel stream, however network connections from pronterface
160 // do not connect to the kernel streams so won't see this FIXME
161 this->reply_stream = THEKERNEL->streams;
162 }
163
164 } else if (gcode->m == 25) { // pause print
165 this->playing_file = false;
166
167 } else if (gcode->m == 26) { // Reset print. Slightly different than M26 in Marlin and the rest
168 if(this->current_file_handler != NULL) {
169 string currentfn = this->filename.c_str();
170 unsigned long old_size = this->file_size;
171
172 // abort the print
173 abort_command("", gcode->stream);
174
175 if(!currentfn.empty()) {
176 // reload the last file opened
177 this->current_file_handler = fopen(currentfn.c_str() , "r");
178
179 if(this->current_file_handler == NULL) {
180 gcode->stream->printf("file.open failed: %s\r\n", currentfn.c_str());
181 } else {
182 this->filename = currentfn;
183 this->file_size = old_size;
184 this->current_stream = nullptr;
185 }
186 }
187 } else {
188 gcode->stream->printf("No file loaded\r\n");
189 }
190
191 } else if (gcode->m == 27) { // report print progress, in format used by Marlin
192 progress_command("-b", gcode->stream);
193
194 } else if (gcode->m == 32) { // select file and start print
195 // Get filename
196 this->filename = "/sd/" + args; // filename is whatever is in args including spaces
197 this->current_stream = nullptr;
198
199 if(this->current_file_handler != NULL) {
200 this->playing_file = false;
201 fclose(this->current_file_handler);
202 }
203
204 this->current_file_handler = fopen( this->filename.c_str(), "r");
205 if(this->current_file_handler == NULL) {
206 gcode->stream->printf("file.open failed: %s\r\n", this->filename.c_str());
207 } else {
208 this->playing_file = true;
209
210 // get size of file
211 int result = fseek(this->current_file_handler, 0, SEEK_END);
212 if (0 != result) {
213 file_size = 0;
214 } else {
215 file_size = ftell(this->current_file_handler);
216 fseek(this->current_file_handler, 0, SEEK_SET);
217 }
218 }
219
220 this->played_cnt = 0;
221 this->elapsed_secs = 0;
222
223 } else if (gcode->m == 600) { // suspend print, Not entirely Marlin compliant, M600.1 will leave the heaters on
224 this->suspend_command((gcode->subcode == 1)?"h":"", gcode->stream);
225
226 } else if (gcode->m == 601) { // resume print
227 this->resume_command("", gcode->stream);
228 }
229
230 }else if(gcode->has_g) {
231 if(gcode->g == 28) { // homing cancels suspend
232 if(this->suspended) {
233 // clean up
234 this->suspended= false;
235 THEROBOT->pop_state();
236 this->saved_temperatures.clear();
237 this->was_playing_file= false;
238 this->suspend_loops= 0;
239 }
240 }
241 }
242 }
243
244 // When a new line is received, check if it is a command, and if it is, act upon it
245 void Player::on_console_line_received( void *argument )
246 {
247 if(THEKERNEL->is_halted()) return; // if in halted state ignore any commands
248
249 SerialMessage new_message = *static_cast<SerialMessage *>(argument);
250
251 string possible_command = new_message.message;
252
253 // ignore anything that is not lowercase or a letter
254 if(possible_command.empty() || !islower(possible_command[0]) || !isalpha(possible_command[0])) {
255 return;
256 }
257
258 string cmd = shift_parameter(possible_command);
259
260 //new_message.stream->printf("Received %s\r\n", possible_command.c_str());
261
262 // Act depending on command
263 if (cmd == "play"){
264 this->play_command( possible_command, new_message.stream );
265 }else if (cmd == "progress"){
266 this->progress_command( possible_command, new_message.stream );
267 }else if (cmd == "abort") {
268 this->abort_command( possible_command, new_message.stream );
269 }else if (cmd == "suspend") {
270 this->suspend_command( possible_command, new_message.stream );
271 }else if (cmd == "resume") {
272 this->resume_command( possible_command, new_message.stream );
273 }
274 }
275
276 // Play a gcode file by considering each line as if it was received on the serial console
277 void Player::play_command( string parameters, StreamOutput *stream )
278 {
279 // extract any options from the line and terminate the line there
280 string options= extract_options(parameters);
281 // Get filename which is the entire parameter line upto any options found or entire line
282 this->filename = absolute_from_relative(parameters);
283
284 if(this->playing_file || this->suspended) {
285 stream->printf("Currently printing, abort print first\r\n");
286 return;
287 }
288
289 if(this->current_file_handler != NULL) { // must have been a paused print
290 fclose(this->current_file_handler);
291 }
292
293 this->current_file_handler = fopen( this->filename.c_str(), "r");
294 if(this->current_file_handler == NULL) {
295 stream->printf("File not found: %s\r\n", this->filename.c_str());
296 return;
297 }
298
299 stream->printf("Playing %s\r\n", this->filename.c_str());
300
301 this->playing_file = true;
302
303 // Output to the current stream if we were passed the -v ( verbose ) option
304 if( options.find_first_of("Vv") == string::npos ) {
305 this->current_stream = nullptr;
306 } else {
307 // we send to the kernels stream as it cannot go away
308 this->current_stream = THEKERNEL->streams;
309 }
310
311 // get size of file
312 int result = fseek(this->current_file_handler, 0, SEEK_END);
313 if (0 != result) {
314 stream->printf("WARNING - Could not get file size\r\n");
315 file_size = 0;
316 } else {
317 file_size = ftell(this->current_file_handler);
318 fseek(this->current_file_handler, 0, SEEK_SET);
319 stream->printf(" File size %ld\r\n", file_size);
320 }
321 this->played_cnt = 0;
322 this->elapsed_secs = 0;
323 }
324
325 void Player::progress_command( string parameters, StreamOutput *stream )
326 {
327
328 // get options
329 string options = shift_parameter( parameters );
330 bool sdprinting= options.find_first_of("Bb") != string::npos;
331
332 if(!playing_file && current_file_handler != NULL) {
333 if(sdprinting)
334 stream->printf("SD printing byte %lu/%lu\r\n", played_cnt, file_size);
335 else
336 stream->printf("SD print is paused at %lu/%lu\r\n", played_cnt, file_size);
337 return;
338
339 } else if(!playing_file) {
340 stream->printf("Not currently playing\r\n");
341 return;
342 }
343
344 if(file_size > 0) {
345 unsigned long est = 0;
346 if(this->elapsed_secs > 10) {
347 unsigned long bytespersec = played_cnt / this->elapsed_secs;
348 if(bytespersec > 0)
349 est = (file_size - played_cnt) / bytespersec;
350 }
351
352 float pcnt = (((float)file_size - (file_size - played_cnt)) * 100.0F) / file_size;
353 // If -b or -B is passed, report in the format used by Marlin and the others.
354 if (!sdprinting) {
355 stream->printf("file: %s, %u %% complete, elapsed time: %02lu:%02lu:%02lu", this->filename.c_str(), (unsigned int)roundf(pcnt), this->elapsed_secs / 3600, (this->elapsed_secs % 3600) / 60, this->elapsed_secs % 60);
356 if(est > 0) {
357 stream->printf(", est time: %02lu:%02lu:%02lu", est / 3600, (est % 3600) / 60, est % 60);
358 }
359 stream->printf("\r\n");
360 } else {
361 stream->printf("SD printing byte %lu/%lu\r\n", played_cnt, file_size);
362 }
363
364 } else {
365 stream->printf("File size is unknown\r\n");
366 }
367 }
368
369 void Player::abort_command( string parameters, StreamOutput *stream )
370 {
371 if(!playing_file && current_file_handler == NULL) {
372 stream->printf("Not currently playing\r\n");
373 return;
374 }
375 suspended= false;
376 playing_file = false;
377 played_cnt = 0;
378 file_size = 0;
379 this->filename = "";
380 this->current_stream = NULL;
381 fclose(current_file_handler);
382 current_file_handler = NULL;
383 if(parameters.empty()) {
384 // clear out the block queue, will wait until queue is empty
385 // MUST be called in on_main_loop to make sure there are no blocked main loops waiting to put something on the queue
386 THEKERNEL->conveyor->flush_queue();
387
388 // now the position will think it is at the last received pos, so we need to do FK to get the actuator position and reset the current position
389 THEROBOT->reset_position_from_current_actuator_position();
390 stream->printf("Aborted playing or paused file. Please turn any heaters off manually\r\n");
391 }
392 }
393
394 void Player::on_main_loop(void *argument)
395 {
396 if(suspended && suspend_loops > 0) {
397 // if we are suspended we need to allow main loop to cycle a few times then finish off the suspend processing
398 if(--suspend_loops == 0) {
399 suspend_part2();
400 return;
401 }
402 }
403
404 if( !this->booted ) {
405 this->booted = true;
406 if( this->on_boot_gcode_enable ) {
407 this->play_command(this->on_boot_gcode, THEKERNEL->serial);
408 } else {
409 //THEKERNEL->serial->printf("On boot gcode disabled! skipping...\n");
410 }
411 }
412
413 if( this->playing_file ) {
414 if(THEKERNEL->is_halted()) {
415 return;
416 }
417
418 char buf[130]; // lines upto 128 characters are allowed, anything longer is discarded
419 bool discard = false;
420
421 while(fgets(buf, sizeof(buf), this->current_file_handler) != NULL) {
422 int len = strlen(buf);
423 if(len == 0) continue; // empty line? should not be possible
424 if(buf[len - 1] == '\n' || feof(this->current_file_handler)) {
425 if(discard) { // we are discarding a long line
426 discard = false;
427 continue;
428 }
429 if(len == 1) continue; // empty line
430
431 if(this->current_stream != nullptr) {
432 this->current_stream->printf("%s", buf);
433 }
434
435 struct SerialMessage message;
436 message.message = buf;
437 message.stream = this->current_stream == nullptr ? &(StreamOutput::NullStream) : this->current_stream;
438
439 // waits for the queue to have enough room
440 THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message);
441 played_cnt += len;
442 return; // we feed one line per main loop
443
444 } else {
445 // discard long line
446 if(this->current_stream != nullptr) { this->current_stream->printf("Warning: Discarded long line\n"); }
447 discard = true;
448 }
449 }
450
451 this->playing_file = false;
452 this->filename = "";
453 played_cnt = 0;
454 file_size = 0;
455 fclose(this->current_file_handler);
456 current_file_handler = NULL;
457 this->current_stream = NULL;
458
459 if(this->reply_stream != NULL) {
460 // if we were printing from an M command from pronterface we need to send this back
461 this->reply_stream->printf("Done printing file\r\n");
462 this->reply_stream = NULL;
463 }
464 }
465 }
466
467 void Player::on_get_public_data(void *argument)
468 {
469 PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
470
471 if(!pdr->starts_with(player_checksum)) return;
472
473 if(pdr->second_element_is(is_playing_checksum) || pdr->second_element_is(is_suspended_checksum)) {
474 static bool bool_data;
475 bool_data = pdr->second_element_is(is_playing_checksum) ? this->playing_file : this->suspended;
476 pdr->set_data_ptr(&bool_data);
477 pdr->set_taken();
478
479 } else if(pdr->second_element_is(get_progress_checksum)) {
480 static struct pad_progress p;
481 if(file_size > 0 && playing_file) {
482 p.elapsed_secs = this->elapsed_secs;
483 float pcnt = (((float)file_size - (file_size - played_cnt)) * 100.0F) / file_size;
484 p.percent_complete = roundf(pcnt);
485 p.filename = this->filename;
486 pdr->set_data_ptr(&p);
487 pdr->set_taken();
488 }
489 }
490 }
491
492 void Player::on_set_public_data(void *argument)
493 {
494 PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
495
496 if(!pdr->starts_with(player_checksum)) return;
497
498 if(pdr->second_element_is(abort_play_checksum)) {
499 abort_command("", &(StreamOutput::NullStream));
500 pdr->set_taken();
501 }
502 }
503
504 /**
505 Suspend a print in progress
506 1. send pause to upstream host, or pause if printing from sd
507 1a. loop on_main_loop several times to clear any buffered commmands
508 2. wait for empty queue
509 3. save the current position, extruder position, temperatures - any state that would need to be restored
510 4. retract by specifed amount either on command line or in config
511 5. turn off heaters.
512 6. optionally run after_suspend gcode (either in config or on command line)
513
514 User may jog or remove and insert filament at this point, extruding or retracting as needed
515
516 */
517 void Player::suspend_command(string parameters, StreamOutput *stream )
518 {
519 if(suspended) {
520 stream->printf("Already suspended\n");
521 return;
522 }
523
524 stream->printf("// Suspending print, waiting for queue to empty...\n");
525
526 // override the leave_heaters_on setting
527 this->override_leave_heaters_on= (parameters == "h");
528
529 suspended= true;
530 if( this->playing_file ) {
531 // pause an sd print
532 this->playing_file = false;
533 this->was_playing_file= true;
534 }else{
535 // send pause to upstream host, we send it on all ports as we don't know which it is on
536 THEKERNEL->streams->printf("// action:pause\r\n");
537 this->was_playing_file= false;
538 }
539
540 // we need to allow main loop to cycle a few times to clear any buffered commands in the serial streams etc
541 suspend_loops= 10;
542 }
543
544 // this completes the suspend
545 void Player::suspend_part2()
546 {
547 // need to use streams here as the original stream may have changed
548 THEKERNEL->streams->printf("// Waiting for queue to empty (Host must stop sending)...\n");
549 // wait for queue to empty
550 THEKERNEL->conveyor->wait_for_idle();
551
552 if(THEKERNEL->is_halted()) {
553 THEKERNEL->streams->printf("Suspend aborted by kill\n");
554 suspended= false;
555 return;
556 }
557
558 THEKERNEL->streams->printf("// Saving current state...\n");
559
560 // save current XYZ position
561 THEROBOT->get_axis_position(this->saved_position);
562
563 // save current extruder state
564 PublicData::set_value( extruder_checksum, save_state_checksum, nullptr );
565
566 // save state use M120
567 THEROBOT->push_state();
568
569 // TODO retract by optional amount...
570
571 this->saved_temperatures.clear();
572 if(!this->leave_heaters_on && !this->override_leave_heaters_on) {
573 // save current temperatures, get a vector of all the controllers data
574 std::vector<struct pad_temperature> controllers;
575 bool ok = PublicData::get_value(temperature_control_checksum, poll_controls_checksum, &controllers);
576 if (ok) {
577 // query each heater and save the target temperature if on
578 for (auto &c : controllers) {
579 // TODO see if in exclude list
580 if(c.target_temperature > 0) {
581 this->saved_temperatures[c.id]= c.target_temperature;
582 }
583 }
584 }
585
586 // turn off heaters that were on
587 for(auto& h : this->saved_temperatures) {
588 float t= 0;
589 PublicData::set_value( temperature_control_checksum, h.first, &t );
590 }
591 }
592
593 // execute optional gcode if defined
594 if(!after_suspend_gcode.empty()) {
595 struct SerialMessage message;
596 message.message = after_suspend_gcode;
597 message.stream = &(StreamOutput::NullStream);
598 THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message );
599 }
600
601 THEKERNEL->streams->printf("// Print Suspended, enter resume to continue printing\n");
602 }
603
604 /**
605 resume the suspended print
606 1. restore the temperatures and wait for them to get up to temp
607 2. optionally run before_resume gcode if specified
608 3. restore the position it was at and E and any other saved state
609 4. resume sd print or send resume upstream
610 */
611 void Player::resume_command(string parameters, StreamOutput *stream )
612 {
613 if(!suspended) {
614 stream->printf("Not suspended\n");
615 return;
616 }
617
618 stream->printf("// resuming print...\n");
619
620 // wait for them to reach temp
621 if(!this->saved_temperatures.empty()) {
622 // set heaters to saved temps
623 for(auto& h : this->saved_temperatures) {
624 float t= h.second;
625 PublicData::set_value( temperature_control_checksum, h.first, &t );
626 }
627 stream->printf("// Waiting for heaters...\n");
628 bool wait= true;
629 uint32_t tus= us_ticker_read(); // mbed call
630 while(wait) {
631 wait= false;
632
633 bool timeup= false;
634 if((us_ticker_read() - tus) >= 1000000) { // print every 1 second
635 timeup= true;
636 tus= us_ticker_read(); // mbed call
637 }
638
639 for(auto& h : this->saved_temperatures) {
640 struct pad_temperature temp;
641 if(PublicData::get_value( temperature_control_checksum, current_temperature_checksum, h.first, &temp )) {
642 if(timeup)
643 stream->printf("%s:%3.1f /%3.1f @%d ", temp.designator.c_str(), temp.current_temperature, ((temp.target_temperature == -1) ? 0.0 : temp.target_temperature), temp.pwm);
644 wait= wait || (temp.current_temperature < h.second);
645 }
646 }
647 if(timeup) stream->printf("\n");
648
649 if(wait)
650 THEKERNEL->call_event(ON_IDLE, this);
651
652 if(THEKERNEL->is_halted()) {
653 // abort temp wait and rest of resume
654 THEKERNEL->streams->printf("Resume aborted by kill\n");
655 THEROBOT->pop_state();
656 this->saved_temperatures.clear();
657 suspended= false;
658 return;
659 }
660 }
661 }
662
663 // clean up
664 this->saved_temperatures.clear();
665
666 if(THEKERNEL->is_halted()) {
667 THEKERNEL->streams->printf("Resume aborted by kill\n");
668 THEROBOT->pop_state();
669 suspended= false;
670 return;
671 }
672
673 // execute optional gcode if defined
674 if(!before_resume_gcode.empty()) {
675 stream->printf("// Executing before resume gcode...\n");
676 struct SerialMessage message;
677 message.message = before_resume_gcode;
678 message.stream = &(StreamOutput::NullStream);
679 THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message );
680 }
681
682 // Restore position
683 stream->printf("// Restoring saved XYZ positions and state...\n");
684 THEROBOT->pop_state();
685 bool abs_mode= THEROBOT->absolute_mode; // what mode we were in
686 // force absolute mode for restoring position, then set to the saved relative/absolute mode
687 THEROBOT->absolute_mode= true;
688 {
689 // NOTE position was saved in MCS so must use G53 to restore position
690 char buf[128];
691 snprintf(buf, sizeof(buf), "G53 G0 X%f Y%f Z%f", saved_position[0], saved_position[1], saved_position[2]);
692 struct SerialMessage message;
693 message.message = buf;
694 message.stream = &(StreamOutput::NullStream);
695 THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message );
696 }
697 THEROBOT->absolute_mode= abs_mode;
698
699 // restore extruder state
700 PublicData::set_value( extruder_checksum, restore_state_checksum, nullptr );
701
702 if(THEKERNEL->is_halted()) {
703 THEKERNEL->streams->printf("Resume aborted by kill\n");
704 suspended= false;
705 return;
706 }
707
708 stream->printf("// Resuming print\n");
709
710 if(this->was_playing_file) {
711 this->playing_file = true;
712 this->was_playing_file= false;
713 }else{
714 // Send resume to host
715 THEKERNEL->streams->printf("// action:resume\r\n");
716 }
717
718 suspended= false;
719 }