1 /*
2     This file is a part of the RepSnapper project.
3     Copyright (C) 2011-12 martin.dieringer@gmx.de
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU Lesser General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU Lesser General Public License for more details.
14 
15     You should have received a copy of the GNU Lesser General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "stdafx.h"
22 #else
23 #define _( t ) t
24 #endif
25 
26 #include <iostream>
27 #include <sstream>
28 #include <stdio.h>
29 #include <string.h>
30 
31 #include "threaded_printer_serial.h"
32 
33 const ntime_t ThreadedPrinterSerial::command_buffer_sleep = { 0, 100 * 1000 * 1000 };
34 const ntime_t ThreadedPrinterSerial::response_buffer_sleep = { 0, 10 * 1000 * 1000 };
35 const ntime_t ThreadedPrinterSerial::log_buffer_sleep = { 0, 10 * 1000 * 1000 };
36 const ntime_t ThreadedPrinterSerial::helper_thread_sleep = { 0, 100 * 1000 * 1000 };
37 
ThreadedPrinterSerial()38 ThreadedPrinterSerial::ThreadedPrinterSerial() :
39   PrinterSerial( helper_thread_sleep.tv_nsec / 1000 / 1000 ),
40   command_buffer( command_buffer_size, command_buffer_sleep, "", false, true ),
41   response_buffer( response_buffer_size, true, response_buffer_sleep, "", true, false ),
42   log_buffer( log_buffer_size, false, log_buffer_sleep, _("\n*** Log overflow ***\n\n"), true, false ),
43   error_buffer( log_buffer_size, true, log_buffer_sleep, _("\n*** Error Log overflow ***\n\n"), true, false ) {
44   request_print = is_printing = printing_complete = false;
45   printer_commands = NULL;
46   pc_lines_printed = 0;
47   pc_bytes_printed = 0;
48   pc_stop_line = 0;
49   inhibit_count = 0;
50 
51   mutex_init( &pc_mutex );
52   mutex_init( &pc_cond_mutex );
53   cond_init( &pc_cond );
54 
55   helper_active = false;
56   helper_cancel = false;
57   return_data = NULL;
58 }
59 
~ThreadedPrinterSerial()60 ThreadedPrinterSerial::~ThreadedPrinterSerial() {
61   if ( helper_active ) {
62     mutex_lock( &pc_cond_mutex );
63     helper_cancel = true;
64     mutex_unlock( &pc_cond_mutex );
65 
66     thread_join( helper_thread );
67     helper_active = false;
68   }
69 
70   mutex_destroy( &pc_mutex );
71   mutex_destroy( &pc_cond_mutex );
72   cond_destroy( &pc_cond );
73 
74   if ( printer_commands != NULL )
75     delete [] printer_commands;
76 }
77 
Connect(string device,int baudrate)78 bool ThreadedPrinterSerial::Connect( string device, int baudrate ) {
79   // Open Serial Port
80   if ( ! PrinterSerial::RawConnect( device, baudrate ) )
81     return false;
82 
83   // Clear printer_commands
84   if ( printer_commands != NULL ) {
85     delete [] printer_commands;
86     printer_commands = NULL;
87   }
88 
89   // Clear/Flush buffers
90   command_buffer.Flush();
91   response_buffer.Flush();
92 
93   helper_cancel = false;
94 
95   // Start thread
96   int rc;
97   if ( ( rc = thread_create( &helper_thread, HelperMainStatic, this ) ) != 0 ) {
98     PrinterSerial::Disconnect();
99     ostringstream os;
100     os << _("Error connecting to printer") << ": ";
101     os << _("Error creating serial helper thread") << ": ";
102     os << strerror( rc ) << endl;
103     LogError( os.str().c_str() );
104     return false;
105   }
106   helper_active = true;
107 
108   return true;
109 }
110 
Disconnect(void)111 void ThreadedPrinterSerial::Disconnect( void ) {
112   StopPrinting( true );
113 
114   if ( helper_active ) {
115     mutex_lock( &pc_cond_mutex );
116     helper_cancel = true;
117     mutex_unlock( &pc_cond_mutex );
118 
119     thread_join( helper_thread );
120     helper_active = false;
121   }
122 
123   command_buffer.Flush();
124 
125   PrinterSerial::Disconnect();
126 }
127 
IsConnected(void)128 bool ThreadedPrinterSerial::IsConnected( void ) {
129   return helper_active && PrinterSerial::IsConnected();
130 }
131 
Reset(void)132 bool ThreadedPrinterSerial::Reset( void ) {
133   StopPrinting( true );
134 
135   if ( ! IsConnected() )
136     return false;
137 
138   if ( helper_active ) {
139     mutex_lock( &pc_cond_mutex );
140     helper_cancel = true;
141     mutex_unlock( &pc_cond_mutex );
142 
143     thread_join( helper_thread );
144     helper_active = false;
145   }
146 
147   command_buffer.Flush();
148   response_buffer.Flush();
149 
150   bool ret = PrinterSerial::RawReset();
151 
152   helper_cancel = false;
153 
154   // Start thread
155   int rc;
156   if ( ( rc = thread_create( &helper_thread, HelperMainStatic, this ) ) != 0 ) {
157     PrinterSerial::Disconnect();
158     ostringstream os;
159     os << _("Error reseting printer") << ": ";
160     os << _("Error creating serial helper thread") << ": ";
161     os << strerror( rc ) << endl;
162     LogError( os.str().c_str() );
163     return false;
164   }
165   helper_active = true;
166 
167   return ret;
168 }
169 
StartPrinting(string commands,unsigned long start_line,unsigned long stop_line)170 bool ThreadedPrinterSerial::StartPrinting( string commands, unsigned long start_line, unsigned long stop_line ) {
171   char *commands_copy;
172   int rc;
173   size_t pos;
174   unsigned long count;
175   unsigned long lines_printed;
176   unsigned long bytes_printed;
177 
178   for ( pos = -1, count = 1; count < start_line; count++ ) {
179     if ( ( pos = commands.find( '\n', pos + 1 ) ) == string::npos ) {
180       char err_buf[ 1024 ];
181       snprintf( err_buf, 1024, _("Error: Cannot start print at line %lu since Gcode only contains %lu lines\n"), start_line, count );
182       if ( err_buf[ 1022 ] != '\0' )
183 	err_buf[ 1022 ] = '\n';
184       err_buf[ 1023 ] = '\0';
185       LogError( err_buf );
186       return false;
187     }
188   }
189 
190   bytes_printed = pos + 1;
191   lines_printed = start_line > 0 ? start_line - 1 : 0;
192 
193   while ( count < stop_line ) {
194     if ( ( pos = commands.find( '\n', pos + 1 ) ) == string::npos ) {
195       if ( commands[ commands.length() - 1 ] != '\n' )
196 	count++;
197       break;
198     }
199     count++;
200   }
201   stop_line = count;
202 
203   // Allocate memory for commands
204   size_t len;
205   len = commands.length();
206   commands_copy = new char[ len + 10 ];
207   memcpy( commands_copy, commands.c_str(), len + 1 );
208 
209   // Make sure we are connected to a printer
210   if ( ! IsConnected() ) {
211     delete [] commands_copy;
212     ostringstream os;
213     os << _("Error starting print") << ": " << _("Printer connection not established") << endl;
214     LogError( os.str().c_str() );
215     return false;
216   }
217 
218   // Lock pc_mutex
219   if ( ( rc = mutex_lock( &pc_mutex ) ) != 0 ) {
220     delete [] commands_copy;
221     ostringstream os;
222     os << _("Error starting print") << ": pc_mutex: " << strerror( rc ) << endl;
223     LogError( os.str().c_str() );
224     return false;
225   }
226 
227   // Lock the cond mutex
228   if ( ( rc = mutex_lock( &pc_cond_mutex ) ) != 0 ) {
229     delete [] commands_copy;
230     mutex_unlock( &pc_mutex );
231     ostringstream os;
232     os << _("Error starting print") << ": pc_cond_mutex: " << strerror( rc ) << endl;
233     LogError( os.str().c_str() );
234     return false;
235   }
236 
237   if ( inhibit_count > 0 ) {
238     delete [] commands_copy;
239     mutex_unlock( &pc_cond_mutex );
240     mutex_unlock( &pc_mutex );
241     return false;
242   }
243 
244   // Make sure we are not already printing
245   if ( is_printing ) {
246     request_print = false;
247 
248     if ( ( rc = cond_wait( &pc_cond, &pc_cond_mutex ) ) !=0 ) {
249       delete [] commands_copy;
250       mutex_unlock( &pc_cond_mutex );
251       mutex_unlock( &pc_mutex );
252       ostringstream os;
253       os << _("Error starting print") << ": cond_wait: " << strerror( rc ) << endl;
254       LogError( os.str().c_str() );
255       return false;
256     }
257   }
258 
259   // Ready to start printing, set the variables
260   if ( printer_commands != NULL )
261     delete [] printer_commands;
262 
263   printer_commands = commands_copy;
264   pc_lines_printed = lines_printed;
265   pc_bytes_printed = bytes_printed;
266   pc_stop_line = stop_line;
267 
268   // Request printing
269   request_print = true;
270 
271   if ( ( rc = cond_wait( &pc_cond, &pc_cond_mutex ) ) !=0 ) {
272     delete [] commands_copy;
273     mutex_unlock( &pc_cond_mutex );
274     mutex_unlock( &pc_mutex );
275     ostringstream os;
276     os << _("Error starting print") << ": cond_wait: " << strerror( rc ) << endl;
277     LogError( os.str().c_str() );
278     return false;
279   }
280 
281   // Unlock mutexes
282   mutex_unlock( &pc_cond_mutex );
283   mutex_unlock( &pc_mutex );
284 
285   return true;
286 }
287 
IsPrinting()288 bool ThreadedPrinterSerial::IsPrinting() {
289   return is_printing && ! printing_complete;
290 }
291 
StopPrinting(bool wait)292 bool ThreadedPrinterSerial::StopPrinting( bool wait ) {
293   int rc;
294 
295   if ( ! IsConnected() ) {
296     return true;
297   }
298 
299   if ( ( rc = mutex_lock( &pc_mutex ) ) != 0 ) {
300     ostringstream os;
301     os << _("Error stopping print") << ": pc_mutex: " << strerror( rc ) << endl;
302     LogError( os.str().c_str() );
303     return false;
304   }
305 
306   if ( ( rc = mutex_lock( &pc_cond_mutex ) ) != 0 ) {
307     mutex_unlock( &pc_mutex );
308     ostringstream os;
309     os << _("Error stopping print") << ": pc_cond_mutex: " << strerror( rc ) << endl;
310     LogError( os.str().c_str() );
311     return false;
312   }
313 
314   request_print = false;
315 
316   if ( wait && is_printing ) {
317     if ( ( rc = cond_wait( &pc_cond, &pc_cond_mutex ) ) !=0 ) {
318       mutex_unlock( &pc_cond_mutex );
319       mutex_unlock( &pc_mutex );
320       ostringstream os;
321       os << _("Error stopping print") << ": cond_wait: " << strerror( rc ) << endl;
322       LogError( os.str().c_str() );
323       return false;
324     }
325   }
326 
327   mutex_unlock( &pc_cond_mutex );
328   mutex_unlock( &pc_mutex );
329   return true;
330 }
331 
ContinuePrinting(bool wait)332 bool ThreadedPrinterSerial::ContinuePrinting( bool wait ) {
333   int rc;
334 
335   if ( printer_commands == NULL ) {
336     ostringstream os;
337     os << _("Error continuing print") << ": ";
338     os << _("No stopped print to continue") << endl;
339     LogError( os.str().c_str() );
340     return false;
341   }
342 
343   if ( ( rc = mutex_lock( &pc_mutex ) ) != 0 ) {
344     ostringstream os;
345     os << _("Error continuing print") << ": pc_mutex: " << strerror( rc ) << endl;
346     LogError( os.str().c_str() );
347     return false;
348   }
349 
350   if ( ! IsConnected() ) {
351     mutex_unlock( &pc_mutex );
352     return false;
353   }
354 
355   if ( ( rc = mutex_lock( &pc_cond_mutex ) ) != 0 ) {
356     mutex_unlock( &pc_mutex );
357     ostringstream os;
358     os << _("Error continuing print") << ": pc_cond_mutex: " << strerror( rc ) << endl;
359     LogError( os.str().c_str() );
360     return false;
361   }
362 
363   if ( inhibit_count > 0 ) {
364     mutex_unlock( &pc_cond_mutex );
365     mutex_unlock( &pc_mutex );
366     return false;
367   }
368 
369   request_print = true;
370 
371   if ( wait && ! is_printing ) {
372     if ( ( rc = cond_wait( &pc_cond, &pc_cond_mutex ) ) !=0 ) {
373       mutex_unlock( &pc_cond_mutex );
374       mutex_unlock( &pc_mutex );
375       ostringstream os;
376       os << _("Error continuing print") << ": cond_wait: " << strerror( rc ) << endl;
377       LogError( os.str().c_str() );
378       return false;
379     }
380   }
381 
382   mutex_unlock( &pc_cond_mutex );
383   mutex_unlock( &pc_mutex );
384   return true;
385 }
386 
Inhibit(bool value)387 void ThreadedPrinterSerial::Inhibit( bool value ) {
388   mutex_lock( &pc_cond_mutex );
389 
390   if ( value )
391     inhibit_count++;
392   else if ( inhibit_count > 0 )
393     inhibit_count--;
394 
395   mutex_unlock( &pc_cond_mutex );
396 }
397 
IsInhibited(void)398 bool ThreadedPrinterSerial::IsInhibited( void ) {
399   mutex_lock( &pc_cond_mutex );
400   bool inhib = inhibit_count > 0;
401   mutex_unlock( &pc_cond_mutex );
402   return inhib;
403 }
404 
GetPrintingProgress(unsigned long * bytes_printed)405 unsigned long ThreadedPrinterSerial::GetPrintingProgress( unsigned long *bytes_printed ) {
406   unsigned long lines;
407   unsigned long bytes;
408 
409   mutex_lock( &pc_cond_mutex );
410 
411   lines = pc_lines_printed;
412   bytes = pc_bytes_printed;
413 
414   mutex_unlock( &pc_cond_mutex );
415 
416   if ( bytes_printed != NULL )
417     *bytes_printed = bytes;
418 
419   return lines;
420 }
421 
GetTotalPrintingLines(void)422 unsigned long ThreadedPrinterSerial::GetTotalPrintingLines( void ) {
423   unsigned long lines;
424 
425   mutex_lock( &pc_cond_mutex );
426   lines = pc_stop_line;
427   mutex_unlock( &pc_cond_mutex );
428 
429   return lines;
430 }
431 
SendAsync(char const * command)432 bool ThreadedPrinterSerial::SendAsync( char const * command) {
433   return command_buffer.Write( command, true );
434 }
435 
Send(string command)436 bool ThreadedPrinterSerial::Send( string command ) {
437   return command_buffer.Write( command.c_str(), true );
438 }
439 
SendAndWaitResponse(string command)440 string ThreadedPrinterSerial::SendAndWaitResponse( string command ) {
441   ThreadBufferReturnData::ReturnData *ret_data = NULL;
442 
443   if ( ! command_buffer.Write( command.c_str(), true, -1, &ret_data ) )
444     return "";
445 
446   if ( ret_data == NULL )
447     return "";
448 
449   if ( ! command_buffer.WaitForReturnData( *ret_data ) ) {
450     delete return_data;
451     return "";
452   }
453 
454   string str = ret_data->GetData();
455   delete ret_data;
456 
457   return str;
458 }
459 
ReadResponse(bool wait)460 string ThreadedPrinterSerial::ReadResponse( bool wait ) {
461   return response_buffer.Read( wait );
462 }
463 
ReadLog(bool wait)464 string ThreadedPrinterSerial::ReadLog( bool wait ) {
465   return log_buffer.Read( wait );
466 }
467 
ReadErrorLog(bool wait)468 string ThreadedPrinterSerial::ReadErrorLog( bool wait ) {
469   return error_buffer.Read( wait );
470 }
471 
472 ////////////////////////////////////////////////////////////////////////////
473 //  Helper Thread Funcitons
474 ////////////////////////////////////////////////////////////////////////////
475 
HelperMainStatic(void * arg)476 void *ThreadedPrinterSerial::HelperMainStatic( void *arg ) {
477   ThreadedPrinterSerial *serial = ( ThreadedPrinterSerial * ) arg;
478 
479   return serial->HelperMain();
480 }
481 
HelperMain(void)482 void *ThreadedPrinterSerial::HelperMain( void ) {
483   // Read start line before continuing
484   // The printer seems to lock up if it recvs a command before the start
485   // line has been sent
486   RecvLine();
487 
488   // Sleep for 10 ms
489   ntime_t nts = { 0, 10 * 1000 * 1000 };
490   nsleep( &nts );
491 
492   // Send version request command
493   strncpy( command_scratch, "M115\n", 6 );
494   SendCommand( false );
495 
496   while ( true ) {
497     if ( return_data != NULL )
498       return_data->AddLine( _("**Internal Error\n") );
499     return_data = NULL;
500 
501     CheckPrintingState();
502 
503     if ( command_buffer.Read( command_scratch, max_command_size, false, &return_data ) > 0 ) {
504       SendCommand( true );
505     } else if ( IsPrinting() ) {
506       SendNextPrinterCommand();
507     } else {
508       nsleep( &helper_thread_sleep );
509     }
510   }
511 
512   return NULL;
513 }
514 
CheckPrintingState(void)515 void ThreadedPrinterSerial::CheckPrintingState( void ) {
516   mutex_lock( &pc_cond_mutex );
517 
518   if ( helper_cancel ) {
519     helper_cancel = false;
520     mutex_unlock( &pc_cond_mutex );
521     if ( return_data != NULL )
522       return_data->AddLine( _("**Connection closed\n") );
523     return_data = NULL;
524     thread_exit();
525   }
526 
527   if ( request_print != is_printing ) {
528     is_printing = request_print;
529     printing_complete = false;
530     cond_broadcast( &pc_cond );
531   }
532 
533   mutex_unlock( &pc_cond_mutex );
534 }
535 
SendNextPrinterCommand(void)536 void ThreadedPrinterSerial::SendNextPrinterCommand( void ) {
537   unsigned long datalen;
538   bool truncated = false;
539 
540   mutex_lock( &pc_cond_mutex );
541 
542   // Find the bounds of the next command
543   const char *start = printer_commands + pc_bytes_printed;
544 
545   const char *stop;
546   for ( stop = start; *stop != '\n' && *stop != '\0'; stop++ )
547     ;
548 
549   datalen = stop - start;
550   if ( datalen > max_command_size - 2 ) {
551     datalen = max_command_size - 2;
552     truncated = true;
553   }
554 
555   // Copy command to scratch buffer.  Always add a newline.
556   memcpy( command_scratch, start, datalen );
557   char *loc = command_scratch + datalen;
558   *loc++ = '\n';
559   *loc++ = '\0';
560 
561   // Update status
562   pc_lines_printed++;
563   pc_bytes_printed = stop - printer_commands + ( ( *stop == '\n' ) ? 1 : 0 );
564 
565   // Update printing complete
566   if ( *stop == '\0' || pc_lines_printed >= pc_stop_line )
567     printing_complete = true;
568 
569   mutex_unlock( &pc_cond_mutex );
570 
571   if ( truncated ) {
572     char warn[ 100 ];
573     snprintf( warn, 99, _("*** Warning: Truncated long printer command at line %lu\n"), pc_lines_printed );
574     if ( warn[ 98 ] != '\0' )
575       warn[ 98 ] = '\n';
576     warn[ 99 ] = '\0';
577     LogLine( warn );
578     LogError( warn );
579   }
580 
581   // Send the command and wait for response
582   SendCommand( false );
583 }
584 
SendCommand(bool buffer_response)585 void ThreadedPrinterSerial::SendCommand( bool buffer_response ) {
586   // Don't send blank lines
587   char *recvd = PrinterSerial::SendCommand();
588 
589   if ( recvd == NULL ) {
590     if ( return_data != NULL )
591       return_data->AddLine( _("**Error sending line\n") );
592     return_data = NULL;
593     return;
594   }
595 
596   if ( strncasecmp( recvd, "!!", 2 ) == 0 ) {
597     // !! Fatal Error
598     response_buffer.Write( recvd, true );
599     if ( return_data != NULL )
600       return_data->AddLine( _("**Fatal Error\n") );
601     return_data = NULL;
602     helper_active = false;
603     Disconnect(); // This is safe.  With helper active false, no mutexes are needed and no threads are killed.
604     thread_exit();
605   }
606 
607   if ( return_data != NULL ) {
608     return_data->AddLine( recvd );
609     return_data = NULL;
610   } else if ( buffer_response ) {
611     // buffer resposne if it is "interesting", that is if it is more than
612     // just the two letter "ok" reply followed by white space.
613     char *loc;
614     for ( loc = recvd + 2; *loc != ' '; loc++ )
615       ;
616 
617     if ( *loc != '\n' )
618       response_buffer.Write( recvd, false );
619   }
620 }
621 
RecvTimeout(void)622 void ThreadedPrinterSerial::RecvTimeout( void ) {
623   CheckPrintingState();
624 }
625 
626 // Log the line.  The provided line should end in a newline character.
LogLine(const char * line)627 void ThreadedPrinterSerial::LogLine( const char *line ) {
628   log_buffer.Write( line, false );
629 }
630 
631 // Log error the line.  The provided line should end in a newline character.
LogError(const char * error_line)632 void ThreadedPrinterSerial::LogError( const char *error_line ) {
633   error_buffer.Write( error_line, false );
634 }
635