Add halt status to panel and a way to clear it
[clinton/Smoothieware.git] / src / modules / utils / player / Player.cpp
1 /*
2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
3 Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4 Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8 #include "Player.h"
9
10 #include "libs/Kernel.h"
11 #include "libs/nuts_bolts.h"
12 #include "libs/utils.h"
13 #include "SerialConsole.h"
14 #include "libs/SerialMessage.h"
15 #include "libs/StreamOutputPool.h"
16 #include "libs/StreamOutput.h"
17 #include "Gcode.h"
18 #include "checksumm.h"
19 #include "Pauser.h"
20 #include "Config.h"
21 #include "ConfigValue.h"
22 #include "SDFAT.h"
23
24 #include "modules/robot/Conveyor.h"
25 #include "DirHandle.h"
26 #include "PublicDataRequest.h"
27 #include "PlayerPublicAccess.h"
28
29 #define on_boot_gcode_checksum CHECKSUM("on_boot_gcode")
30 #define on_boot_gcode_enable_checksum CHECKSUM("on_boot_gcode_enable")
31
32 extern SDFAT mounter;
33
34 void Player::on_module_loaded()
35 {
36 this->playing_file = false;
37 this->current_file_handler = NULL;
38 this->booted = false;
39 this->register_for_event(ON_CONSOLE_LINE_RECEIVED);
40 this->register_for_event(ON_MAIN_LOOP);
41 this->register_for_event(ON_SECOND_TICK);
42 this->register_for_event(ON_GET_PUBLIC_DATA);
43 this->register_for_event(ON_SET_PUBLIC_DATA);
44 this->register_for_event(ON_GCODE_RECEIVED);
45 this->register_for_event(ON_HALT);
46
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();
49 this->elapsed_secs = 0;
50 this->reply_stream = NULL;
51 }
52
53 void Player::on_halt(void *)
54 {
55 abort_command("", &(StreamOutput::NullStream));
56 }
57
58 void Player::on_second_tick(void *)
59 {
60 if (!THEKERNEL->pauser->paused()) this->elapsed_secs++;
61 }
62
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
66 string 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
78 void Player::on_gcode_received(void *argument)
79 {
80 Gcode *gcode = static_cast<Gcode *>(argument);
81 string args = get_arguments(gcode->get_command());
82 if (gcode->has_m) {
83 if (gcode->m == 21) { // Dummy code; makes Octoprint happy -- supposed to initialize SD card
84 gcode->mark_as_taken();
85 mounter.remount();
86 gcode->stream->printf("SD card ok\r\n");
87
88 } else if (gcode->m == 23) { // select file
89 gcode->mark_as_taken();
90 this->filename = "/sd/" + args; // filename is whatever is in args
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 }
97 this->current_file_handler = fopen( this->filename.c_str(), "r");
98
99 if(this->current_file_handler == NULL) {
100 gcode->stream->printf("file.open failed: %s\r\n", this->filename.c_str());
101 return;
102
103 } else {
104 // get size of file
105 int result = fseek(this->current_file_handler, 0, SEEK_END);
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);
111 }
112 gcode->stream->printf("File opened:%s Size:%ld\r\n", this->filename.c_str(), this->file_size);
113 gcode->stream->printf("File selected\r\n");
114 }
115
116
117 this->played_cnt = 0;
118 this->elapsed_secs = 0;
119
120 } else if (gcode->m == 24) { // start print
121 gcode->mark_as_taken();
122 if (this->current_file_handler != NULL) {
123 this->playing_file = true;
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
127 this->reply_stream = THEKERNEL->streams;
128 }
129
130 } else if (gcode->m == 25) { // pause print
131 gcode->mark_as_taken();
132 this->playing_file = false;
133
134 } else if (gcode->m == 26) { // Reset print. Slightly different than M26 in Marlin and the rest
135 gcode->mark_as_taken();
136 if(this->current_file_handler != NULL) {
137 string currentfn = this->filename.c_str();
138 unsigned long old_size = this->file_size;
139
140 // abort the print
141 abort_command("", gcode->stream);
142
143 if(!currentfn.empty()) {
144 // reload the last file opened
145 this->current_file_handler = fopen(currentfn.c_str() , "r");
146
147 if(this->current_file_handler == NULL) {
148 gcode->stream->printf("file.open failed: %s\r\n", currentfn.c_str());
149 } else {
150 this->filename = currentfn;
151 this->file_size = old_size;
152 this->current_stream = &(StreamOutput::NullStream);
153 }
154 }
155
156 } else {
157 gcode->stream->printf("No file loaded\r\n");
158 }
159
160 } else if (gcode->m == 27) { // report print progress, in format used by Marlin
161 gcode->mark_as_taken();
162 progress_command("-b", gcode->stream);
163
164 } else if (gcode->m == 32) { // select file and start print
165 gcode->mark_as_taken();
166 // Get filename
167 this->filename = "/sd/" + args; // filename is whatever is in args including spaces
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");
176 if(this->current_file_handler == NULL) {
177 gcode->stream->printf("file.open failed: %s\r\n", this->filename.c_str());
178 } else {
179 this->playing_file = true;
180 }
181 }
182 }
183 }
184
185 // When a new line is received, check if it is a command, and if it is, act upon it
186 void Player::on_console_line_received( void *argument )
187 {
188 SerialMessage new_message = *static_cast<SerialMessage *>(argument);
189
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;
193
194 string possible_command = new_message.message;
195 string cmd = shift_parameter(possible_command);
196
197 //new_message.stream->printf("Received %s\r\n", possible_command.c_str());
198
199 // Act depending on command
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 );
206 }
207
208 // Play a gcode file by considering each line as if it was received on the serial console
209 void Player::play_command( string parameters, StreamOutput *stream )
210 {
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);
215
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
225 this->current_file_handler = fopen( this->filename.c_str(), "r");
226 if(this->current_file_handler == NULL) {
227 stream->printf("File not found: %s\r\n", this->filename.c_str());
228 return;
229 }
230
231 stream->printf("Playing %s\r\n", this->filename.c_str());
232
233 this->playing_file = true;
234
235 // Output to the current stream if we were passed the -v ( verbose ) option
236 if( options.find_first_of("Vv") == string::npos ) {
237 this->current_stream = &(StreamOutput::NullStream);
238 } else {
239 // we send to the kernels stream as it cannot go away
240 this->current_stream = THEKERNEL->streams;
241 }
242
243 // get size of file
244 int result = fseek(this->current_file_handler, 0, SEEK_END);
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);
252 }
253 this->played_cnt = 0;
254 this->elapsed_secs = 0;
255 }
256
257 void Player::progress_command( string parameters, StreamOutput *stream )
258 {
259
260 // get options
261 string options = shift_parameter( parameters );
262 bool sdprinting= options.find_first_of("Bb") != string::npos;
263
264 if(!playing_file && current_file_handler != NULL) {
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);
269 return;
270
271 } else if(!playing_file) {
272 stream->printf("Not currently playing\r\n");
273 return;
274 }
275
276 if(file_size > 0) {
277 unsigned long est = 0;
278 if(this->elapsed_secs > 10) {
279 unsigned long bytespersec = played_cnt / this->elapsed_secs;
280 if(bytespersec > 0)
281 est = (file_size - played_cnt) / bytespersec;
282 }
283
284 unsigned int pcnt = (file_size - (file_size - played_cnt)) * 100 / file_size;
285 // If -b or -B is passed, report in the format used by Marlin and the others.
286 if (!sdprinting) {
287 stream->printf("%u %% complete, elapsed time: %lu s", pcnt, this->elapsed_secs);
288 if(est > 0) {
289 stream->printf(", est time: %lu s", est);
290 }
291 stream->printf("\r\n");
292 } else {
293 stream->printf("SD printing byte %lu/%lu\r\n", played_cnt, file_size);
294 }
295
296 } else {
297 stream->printf("File size is unknown\r\n");
298 }
299 }
300
301 void Player::abort_command( string parameters, StreamOutput *stream )
302 {
303 if(!playing_file && current_file_handler == NULL) {
304 stream->printf("Not currently playing\r\n");
305 return;
306 }
307 playing_file = false;
308 played_cnt = 0;
309 file_size = 0;
310 this->filename = "";
311 this->current_stream = NULL;
312 fclose(current_file_handler);
313 current_file_handler = NULL;
314 stream->printf("Aborted playing or paused file\r\n");
315 }
316
317 void Player::on_main_loop(void *argument)
318 {
319 if( !this->booted ) {
320 this->booted = true;
321 if( this->on_boot_gcode_enable ) {
322 this->play_command(this->on_boot_gcode, THEKERNEL->serial);
323 } else {
324 //THEKERNEL->serial->printf("On boot gcode disabled! skipping...\n");
325 }
326 }
327
328 if( this->playing_file ) {
329 char buf[130]; // lines upto 128 characters are allowed, anything longer is discarded
330 bool discard = false;
331
332 while(fgets(buf, sizeof(buf), this->current_file_handler) != NULL) {
333 int len = strlen(buf);
334 if(len == 0) continue; // empty line? should not be possible
335 if(buf[len - 1] == '\n' || feof(this->current_file_handler)) {
336 if(discard) { // we are discarding a long line
337 discard = false;
338 continue;
339 }
340 if(len == 1) continue; // empty line
341
342 this->current_stream->printf("%s", buf);
343 struct SerialMessage message;
344 message.message = buf;
345 message.stream = this->current_stream;
346
347 // waits for the queue to have enough room
348 THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message);
349 played_cnt += len;
350 return; // we feed one line per main loop
351
352 } else {
353 // discard long line
354 this->current_stream->printf("Warning: Discarded long line\n");
355 discard = true;
356 }
357 }
358
359 this->playing_file = false;
360 this->filename = "";
361 played_cnt = 0;
362 file_size = 0;
363 fclose(this->current_file_handler);
364 current_file_handler = NULL;
365 this->current_stream = NULL;
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");
370 this->reply_stream = NULL;
371 }
372 }
373 }
374
375 void Player::on_get_public_data(void *argument)
376 {
377 PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
378
379 if(!pdr->starts_with(player_checksum)) return;
380
381 if(pdr->second_element_is(is_playing_checksum)) {
382 static bool bool_data;
383 bool_data = this->playing_file;
384 pdr->set_data_ptr(&bool_data);
385 pdr->set_taken();
386
387 } else if(pdr->second_element_is(get_progress_checksum)) {
388 static struct pad_progress p;
389 if(file_size > 0 && playing_file) {
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;
393 pdr->set_data_ptr(&p);
394 pdr->set_taken();
395 }
396 }
397 }
398
399 void Player::on_set_public_data(void *argument)
400 {
401 PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
402
403 if(!pdr->starts_with(player_checksum)) return;
404
405 if(pdr->second_element_is(abort_play_checksum)) {
406 abort_command("", &(StreamOutput::NullStream));
407 pdr->set_taken();
408 }
409 }