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