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