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" | |
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 | 45 | extern SDFAT mounter; |
46 | ||
02e4b295 | 47 | Player::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 | ||
58 | void 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 |
78 | void Player::on_halt(void* argument) |
79 | { | |
80 | if(argument == nullptr && this->playing_file ) { | |
81 | abort_command("1", &(StreamOutput::NullStream)); | |
82 | } | |
83 | } | |
84 | ||
36aa8b0b JM |
85 | void 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 | |
93 | string 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 |
105 | void 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 |
235 | void 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 |
267 | void 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 |
315 | void 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 |
359 | void 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 |
384 | void 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 |
457 | void 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 |
482 | void 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 | /** | |
495 | Suspend a print in progress | |
496 | 1. send pause to upstream host, or pause if printing from sd | |
0f11f650 | 497 | 1a. loop on_main_loop several times to clear any buffered commmands |
02e4b295 JM |
498 | 2. wait for empty queue |
499 | 3. save the current position, extruder position, temperatures - any state that would need to be restored | |
500 | 4. retract by specifed amount either on command line or in config | |
501 | 5. turn off heaters. | |
d467fcad | 502 | 6. optionally run after_suspend gcode (either in config or on command line) |
02e4b295 JM |
503 | |
504 | User may jog or remove and insert filament at this point, extruding or retracting as needed | |
505 | ||
506 | */ | |
507 | void 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 | |
535 | void 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 | /** | |
589 | resume the suspended print | |
590 | 1. restore the temperatures and wait for them to get up to temp | |
d467fcad JM |
591 | 2. optionally run before_resume gcode if specified |
592 | 3. restore the position it was at and E and any other saved state | |
593 | 4. resume sd print or send resume upstream | |
02e4b295 JM |
594 | */ |
595 | void 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 | } |