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