/ octoprint_GPX / gpxprinter.py
gpxprinter.py
1 # coding=utf-8 2 from __future__ import absolute_import 3 __author__ = "Mark Walker <markwal@hotmail.com> based on work by Gina Häußge" 4 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' 5 6 import os 7 import logging 8 import time 9 import Queue 10 import re 11 import datetime 12 13 from octoprint.filemanager import FileDestinations 14 15 try: 16 import gpx 17 except: 18 pass 19 20 class GpxPrinter(): 21 def __init__(self, gpx_plugin, port = None, baudrate = None, timeout = 0): 22 self._logger = gpx_plugin._logger 23 self._settings = gpx_plugin._settings 24 self._printer = gpx_plugin._printer 25 self._bot_cancelled = False 26 if not gpx: 27 self._logger.warn("Unable to import gpx module") 28 raise ValueError("Unable to import gpx module") 29 self.port = port 30 self.baudrate = self._baudrate = baudrate 31 self.timeout = timeout 32 self._logger.info("GPXPrinter created, port: %s, baudrate: %s" % (self.port, self.baudrate)) 33 self.outgoing = Queue.Queue() 34 self.baudrateError = False; 35 data_folder = gpx_plugin.get_plugin_data_folder() 36 self.profile_path = os.path.join(data_folder, "gpx.ini") 37 log_path = self._settings.get_plugin_logfile_path() 38 self._regex_linenumber = re.compile("N(\d+)") 39 try: 40 self._logger.debug("Calling gpx.connect") 41 self._append(gpx.connect(port, baudrate, self.profile_path, log_path, 42 self._settings.get_boolean(["verbose"]))) 43 time.sleep(float(self._settings.get(["connection_pause"]))) 44 self._append(gpx.start()) 45 self._logger.info("gpx.connect succeeded") 46 except Exception as e: 47 self._logger.info("gpx.connect raised exception = %s" % e) 48 raise 49 50 def refresh_ini(self): 51 if not self._printer.is_printing() and not self._printer.is_paused(): 52 gpx.reset_ini() 53 gpx.read_ini(self.profile_path) 54 55 def _bot_reports_build_cancelled(self): 56 # sometimes the bot tells us the build is cancelled because it wants us 57 # to stop sending commands (user cancelled from LCD, something bad 58 # happend, etc.) but sometimes, it is just reporting that it complied 59 # with our request to stop. To avoid looping endlessly we only take action 60 # if there is something for us to stop. 61 if self._printer.is_printing(): 62 currentOrigin = None 63 currentJob = self._printer.get_current_job() 64 if currentJob is not None and "file" in currentJob.keys(): 65 currentJobFile = currentJob["file"] 66 if "origin" in currentJobFile.keys(): 67 currentOrigin = currentJobFile["origin"] 68 if currentOrigin != FileDestinations.SDCARD: 69 self._bot_cancelled = True 70 self._printer.cancel_print() 71 72 def clear_bot_cancelled(self): 73 # called when a new print is started. We'll just assume the user knows 74 # what they're doing and the cancel has completed. 75 self._bot_cancelled = False 76 77 def progress(self, percent): 78 # we don't want the progress event to pre-empt the build start or 79 # override the build end notification and the M73 causes a build start 80 # if we aren't already running one 81 if gpx.build_started(): 82 try: 83 # loop sending for a while if the queue isn't full or if the bot 84 # isn't listening 85 for i in range(0, 10): 86 try: 87 gpx.write("M73 P%d" % percent) 88 break 89 except gpx.BufferOverflow: 90 time.sleep(0.01) 91 except gpx.Timeout: 92 time.sleep(0.1) 93 except gpx.CancelBuild: 94 self._bot_reports_build_cancelled() 95 96 def _append(self, s): 97 if (s != ''): 98 for item in s.split('\n'): 99 self.outgoing.put(item) 100 101 def write(self, data): 102 try: 103 rval = len(data) 104 data = data.strip() 105 if (self.baudrate != self._baudrate): 106 try: 107 self._baudrate = self.baudrate 108 self._logger.info("new baudrate = %d" % self.baudrate) 109 gpx.set_baudrate(self.baudrate) 110 self.baudrateError = False 111 except ValueError: 112 self.baudrateError = True 113 self.outgoing.put('') 114 return 0 115 116 # look for a line number 117 # line number means OctoPrint is streaming gcode at us (gpx.ini flavor) 118 # no line number means OctoPrint is generating the gcode (reprap flavor) 119 match = self._regex_linenumber.match(data) 120 121 # try to talk to the bot 122 try: 123 if match is None: 124 reprapSave = gpx.reprap_flavor(True) 125 126 # loop sending until the queue isn't full 127 timeout_retries = 0 128 bo_retries = 0 129 while True: 130 try: 131 self._append(gpx.write("%s" % data)) 132 break 133 except gpx.BufferOverflow: 134 bo_retries += 1 135 try: 136 if gpx.build_paused(): 137 if bo_retries == 1: 138 self._append("// echo: print paused at bot") 139 time.sleep(1) # 1 sec 140 except IOError: 141 pass 142 time.sleep(0.1) # 100 ms 143 except gpx.Timeout: 144 time.sleep(1) 145 timeout_retries += 1 146 if (timeout_retries >= 5): 147 raise 148 149 finally: 150 if match is None: 151 gpx.reprap_flavor(reprapSave) 152 except gpx.CancelBuild: 153 self._bot_reports_build_cancelled() 154 return rval 155 156 def readline(self): 157 try: 158 if (self.baudrateError): 159 if (self._baudrate != self.baudrate): 160 gpx.write("M105") 161 return '' 162 163 try: 164 return self.outgoing.get_nowait() 165 except Queue.Empty: 166 pass 167 168 if gpx.listing_files(): 169 return gpx.readnext() 170 171 while True: 172 timeout = 2 if gpx.waiting else self.timeout 173 try: 174 return self.outgoing.get(timeout=timeout) 175 except Queue.Empty: 176 self._append(gpx.readnext()) 177 178 except gpx.CancelBuild: 179 self._bot_reports_build_cancelled() 180 return '// echo: build cancelled' 181 182 def cancel(self): 183 self._logger.warn("Cancelling build %s", "by the printer" if self._bot_cancelled else "by OctoPrint") 184 if not self._bot_cancelled: 185 if self._settings.get_boolean(['extended_stop_instead_of_abort']): 186 # make sure if you use this you have your own "gcode on cancel" 187 # that turns off motors and heaters 188 self._logger.debug("stop") 189 self._append(gpx.stop()) 190 else: 191 self._logger.debug("abort") 192 self._append(gpx.abort()) 193 self._bot_cancelled = False; 194 195 def close(self): 196 gpx.disconnect() 197 return