/ 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