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