2012-12-25 10:55:00 +00:00
|
|
|
# coding=utf-8
|
2012-12-28 19:37:40 +00:00
|
|
|
__author__ = "Gina Häußge <osd@foosel.net>"
|
2013-01-01 20:04:00 +00:00
|
|
|
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
2012-12-25 10:55:00 +00:00
|
|
|
|
|
|
|
import time
|
2013-01-11 23:00:58 +00:00
|
|
|
import datetime
|
|
|
|
import threading
|
|
|
|
import copy
|
|
|
|
import os
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2013-01-18 22:23:50 +00:00
|
|
|
import octoprint.util.comm as comm
|
2013-01-30 19:56:17 +00:00
|
|
|
import octoprint.util as util
|
2013-01-01 20:04:00 +00:00
|
|
|
|
2013-01-18 22:23:50 +00:00
|
|
|
from octoprint.settings import settings
|
2012-12-28 19:37:40 +00:00
|
|
|
|
|
|
|
def getConnectionOptions():
|
|
|
|
"""
|
|
|
|
Retrieves the available ports, baudrates, prefered port and baudrate for connecting to the printer.
|
|
|
|
"""
|
|
|
|
return {
|
2012-12-31 12:18:54 +00:00
|
|
|
"ports": comm.serialList(),
|
|
|
|
"baudrates": comm.baudrateList(),
|
2013-02-16 19:28:09 +00:00
|
|
|
"portPreference": settings().get(["serial", "port"]),
|
|
|
|
"baudratePreference": settings().getInt(["serial", "baudrate"])
|
2012-12-28 19:37:40 +00:00
|
|
|
}
|
2012-12-25 10:55:00 +00:00
|
|
|
|
|
|
|
class Printer():
|
2013-01-30 19:56:17 +00:00
|
|
|
def __init__(self, gcodeManager):
|
|
|
|
self._gcodeManager = gcodeManager
|
|
|
|
|
2012-12-25 10:55:00 +00:00
|
|
|
# state
|
2013-01-06 15:51:04 +00:00
|
|
|
self._temp = None
|
|
|
|
self._bedTemp = None
|
|
|
|
self._targetTemp = None
|
|
|
|
self._targetBedTemp = None
|
|
|
|
self._temps = {
|
2012-12-28 19:37:40 +00:00
|
|
|
"actual": [],
|
|
|
|
"target": [],
|
|
|
|
"actualBed": [],
|
|
|
|
"targetBed": []
|
2012-12-25 10:55:00 +00:00
|
|
|
}
|
2013-01-10 22:40:00 +00:00
|
|
|
self._tempBacklog = []
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
self._latestMessage = None
|
|
|
|
self._messages = []
|
2013-01-10 22:40:00 +00:00
|
|
|
self._messageBacklog = []
|
2013-01-06 15:51:04 +00:00
|
|
|
|
|
|
|
self._latestLog = None
|
|
|
|
self._log = []
|
2013-01-10 22:40:00 +00:00
|
|
|
self._logBacklog = []
|
2013-01-06 15:51:04 +00:00
|
|
|
|
|
|
|
self._state = None
|
|
|
|
|
|
|
|
self._currentZ = None
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
self._progress = None
|
|
|
|
self._printTime = None
|
|
|
|
self._printTimeLeft = None
|
2012-12-28 19:37:40 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
# gcode handling
|
|
|
|
self._gcodeList = None
|
|
|
|
self._filename = None
|
|
|
|
self._gcodeLoader = None
|
2012-12-29 14:41:23 +00:00
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
# sd handling
|
|
|
|
self._sdPrinting = False
|
|
|
|
self._sdFile = None
|
|
|
|
self._sdStreamer = None
|
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
# feedrate
|
|
|
|
self._feedrateModifierMapping = {"outerWall": "WALL-OUTER", "innerWall": "WALL_INNER", "fill": "FILL", "support": "SUPPORT"}
|
|
|
|
|
|
|
|
# timelapse
|
|
|
|
self._timelapse = None
|
2013-01-03 14:25:20 +00:00
|
|
|
|
2012-12-25 10:55:00 +00:00
|
|
|
# comm
|
2013-01-06 15:51:04 +00:00
|
|
|
self._comm = None
|
|
|
|
|
|
|
|
# callbacks
|
|
|
|
self._callbacks = []
|
2013-01-06 16:11:32 +00:00
|
|
|
self._lastProgressReport = None
|
|
|
|
|
2013-01-11 23:00:58 +00:00
|
|
|
self._stateMonitor = StateMonitor(
|
|
|
|
ratelimit=0.5,
|
|
|
|
updateCallback=self._sendCurrentDataCallbacks,
|
|
|
|
addTemperatureCallback=self._sendAddTemperatureCallbacks,
|
|
|
|
addLogCallback=self._sendAddLogCallbacks,
|
|
|
|
addMessageCallback=self._sendAddMessageCallbacks
|
|
|
|
)
|
2013-01-10 22:40:00 +00:00
|
|
|
self._stateMonitor.reset(
|
2013-01-11 23:00:58 +00:00
|
|
|
state={"state": None, "stateString": self.getStateString(), "flags": self._getStateFlags()},
|
|
|
|
jobData={"filename": None, "lines": None, "estimatedPrintTime": None, "filament": None},
|
|
|
|
gcodeData={"filename": None, "progress": None},
|
2013-05-20 17:18:03 +00:00
|
|
|
sdUploadData={"filename": None, "progress": None},
|
2013-01-11 23:00:58 +00:00
|
|
|
progress={"progress": None, "printTime": None, "printTimeLeft": None},
|
|
|
|
currentZ=None
|
2013-01-10 22:40:00 +00:00
|
|
|
)
|
2013-01-06 20:19:39 +00:00
|
|
|
|
|
|
|
#~~ callback handling
|
2013-01-06 15:51:04 +00:00
|
|
|
|
|
|
|
def registerCallback(self, callback):
|
|
|
|
self._callbacks.append(callback)
|
|
|
|
self._sendInitialStateUpdate(callback)
|
|
|
|
|
|
|
|
def unregisterCallback(self, callback):
|
|
|
|
if callback in self._callbacks:
|
|
|
|
self._callbacks.remove(callback)
|
|
|
|
|
2013-01-11 23:00:58 +00:00
|
|
|
def _sendAddTemperatureCallbacks(self, data):
|
|
|
|
for callback in self._callbacks:
|
|
|
|
try: callback.addTemperature(data)
|
|
|
|
except: pass
|
|
|
|
|
|
|
|
def _sendAddLogCallbacks(self, data):
|
|
|
|
for callback in self._callbacks:
|
|
|
|
try: callback.addLog(data)
|
|
|
|
except: pass
|
|
|
|
|
|
|
|
def _sendAddMessageCallbacks(self, data):
|
|
|
|
for callback in self._callbacks:
|
|
|
|
try: callback.addMessage(data)
|
|
|
|
except: pass
|
|
|
|
|
2013-01-10 22:40:00 +00:00
|
|
|
def _sendCurrentDataCallbacks(self, data):
|
2013-01-06 20:19:39 +00:00
|
|
|
for callback in self._callbacks:
|
2013-01-11 23:00:58 +00:00
|
|
|
try: callback.sendCurrentData(copy.deepcopy(data))
|
2013-01-06 20:19:39 +00:00
|
|
|
except: pass
|
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
def _sendTriggerUpdateCallbacks(self, type):
|
|
|
|
for callback in self._callbacks:
|
|
|
|
try: callback.sendUpdateTrigger(type)
|
|
|
|
except: pass
|
|
|
|
|
|
|
|
#~~ printer commands
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2012-12-28 19:37:40 +00:00
|
|
|
def connect(self, port=None, baudrate=None):
|
|
|
|
"""
|
|
|
|
Connects to the printer. If port and/or baudrate is provided, uses these settings, otherwise autodetection
|
|
|
|
will be attempted.
|
|
|
|
"""
|
2013-01-06 15:51:04 +00:00
|
|
|
if self._comm is not None:
|
|
|
|
self._comm.close()
|
|
|
|
self._comm = comm.MachineCom(port, baudrate, callbackObject=self)
|
2012-12-25 10:55:00 +00:00
|
|
|
|
|
|
|
def disconnect(self):
|
2012-12-28 19:37:40 +00:00
|
|
|
"""
|
|
|
|
Closes the connection to the printer.
|
|
|
|
"""
|
2013-01-06 15:51:04 +00:00
|
|
|
if self._comm is not None:
|
|
|
|
self._comm.close()
|
|
|
|
self._comm = None
|
2012-12-25 10:55:00 +00:00
|
|
|
|
|
|
|
def command(self, command):
|
2012-12-28 19:37:40 +00:00
|
|
|
"""
|
|
|
|
Sends a single gcode command to the printer.
|
|
|
|
"""
|
2012-12-26 14:03:34 +00:00
|
|
|
self.commands([command])
|
|
|
|
|
|
|
|
def commands(self, commands):
|
2012-12-28 19:37:40 +00:00
|
|
|
"""
|
|
|
|
Sends multiple gcode commands (provided as a list) to the printer.
|
|
|
|
"""
|
2012-12-26 14:03:34 +00:00
|
|
|
for command in commands:
|
2013-01-06 15:51:04 +00:00
|
|
|
self._comm.sendCommand(command)
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2012-12-29 14:41:23 +00:00
|
|
|
def setFeedrateModifier(self, structure, percentage):
|
2013-01-06 15:51:04 +00:00
|
|
|
if (not self._feedrateModifierMapping.has_key(structure)) or percentage < 0:
|
2012-12-29 14:41:23 +00:00
|
|
|
return
|
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
self._comm.setFeedrateModifier(self._feedrateModifierMapping[structure], percentage / 100.0)
|
|
|
|
|
2013-03-30 17:21:49 +00:00
|
|
|
def loadGcode(self, file, printAfterLoading=False):
|
2013-01-06 15:51:04 +00:00
|
|
|
"""
|
|
|
|
Loads the gcode from the given file as the new print job.
|
|
|
|
Aborts if the printer is currently printing or another gcode file is currently being loaded.
|
|
|
|
"""
|
|
|
|
if (self._comm is not None and self._comm.isPrinting()) or (self._gcodeLoader is not None):
|
|
|
|
return
|
|
|
|
|
2013-05-21 21:41:18 +00:00
|
|
|
self._sdFile = None
|
2013-02-03 20:14:22 +00:00
|
|
|
self._setJobData(None, None)
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-03-30 17:21:49 +00:00
|
|
|
onGcodeLoadedCallback = self._onGcodeLoaded
|
|
|
|
if printAfterLoading:
|
|
|
|
onGcodeLoadedCallback = self._onGcodeLoadedToPrint
|
2013-03-26 05:09:36 +00:00
|
|
|
|
2013-03-30 17:21:49 +00:00
|
|
|
self._gcodeLoader = GcodeLoader(file, self._onGcodeLoadingProgress, onGcodeLoadedCallback)
|
2013-03-26 05:09:36 +00:00
|
|
|
self._gcodeLoader.start()
|
|
|
|
|
|
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
def startPrint(self):
|
|
|
|
"""
|
|
|
|
Starts the currently loaded print job.
|
|
|
|
Only starts if the printer is connected and operational, not currently printing and a printjob is loaded
|
|
|
|
"""
|
|
|
|
if self._comm is None or not self._comm.isOperational():
|
|
|
|
return
|
2013-05-20 17:18:03 +00:00
|
|
|
if self._gcodeList is None and self._sdFile is None:
|
2013-01-06 15:51:04 +00:00
|
|
|
return
|
|
|
|
if self._comm.isPrinting():
|
|
|
|
return
|
|
|
|
|
2013-06-09 16:13:12 +00:00
|
|
|
self._setCurrentZ(None)
|
2013-05-20 17:18:03 +00:00
|
|
|
if self._sdFile is not None:
|
|
|
|
# we are working in sd mode
|
|
|
|
self._sdPrinting = True
|
|
|
|
self._comm.printSdFile()
|
|
|
|
else:
|
|
|
|
# we are working in local mode
|
|
|
|
self._comm.printGCode(self._gcodeList)
|
2013-01-06 15:51:04 +00:00
|
|
|
|
|
|
|
def togglePausePrint(self):
|
|
|
|
"""
|
|
|
|
Pause the current printjob.
|
|
|
|
"""
|
|
|
|
if self._comm is None:
|
|
|
|
return
|
|
|
|
self._comm.setPause(not self._comm.isPaused())
|
|
|
|
|
|
|
|
def cancelPrint(self, disableMotorsAndHeater=True):
|
|
|
|
"""
|
|
|
|
Cancel the current printjob.
|
|
|
|
"""
|
|
|
|
if self._comm is None:
|
|
|
|
return
|
2013-05-20 17:18:03 +00:00
|
|
|
|
|
|
|
if self._sdPrinting:
|
|
|
|
self._sdPrinting = False
|
2013-01-06 15:51:04 +00:00
|
|
|
self._comm.cancelPrint()
|
2013-05-20 17:18:03 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
if disableMotorsAndHeater:
|
2013-01-13 17:29:11 +00:00
|
|
|
self.commands(["M84", "M104 S0", "M140 S0", "M106 S0"]) # disable motors, switch off heaters and fan
|
2013-01-06 15:51:04 +00:00
|
|
|
|
|
|
|
# reset line, height, print time
|
|
|
|
self._setCurrentZ(None)
|
2013-05-20 17:18:03 +00:00
|
|
|
self._setProgressData(None, None, None, None)
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-01-30 19:56:17 +00:00
|
|
|
# mark print as failure
|
2013-03-16 17:25:39 +00:00
|
|
|
if self._filename is not None:
|
|
|
|
self._gcodeManager.printFailed(self._filename)
|
2013-01-30 19:56:17 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
#~~ state monitoring
|
2012-12-29 14:41:23 +00:00
|
|
|
|
2013-01-03 14:25:20 +00:00
|
|
|
def setTimelapse(self, timelapse):
|
2013-01-06 15:51:04 +00:00
|
|
|
if self._timelapse is not None and self.isPrinting():
|
|
|
|
self._timelapse.onPrintjobStopped()
|
|
|
|
del self._timelapse
|
|
|
|
self._timelapse = timelapse
|
2013-01-03 14:25:20 +00:00
|
|
|
|
|
|
|
def getTimelapse(self):
|
2013-01-06 15:51:04 +00:00
|
|
|
return self._timelapse
|
2013-01-03 14:25:20 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
def _setCurrentZ(self, currentZ):
|
|
|
|
self._currentZ = currentZ
|
2013-01-11 23:00:58 +00:00
|
|
|
|
|
|
|
formattedCurrentZ = None
|
|
|
|
if self._currentZ:
|
|
|
|
formattedCurrentZ = "%.2f mm" % (self._currentZ)
|
|
|
|
self._stateMonitor.setCurrentZ(formattedCurrentZ)
|
2013-01-06 15:51:04 +00:00
|
|
|
|
|
|
|
def _setState(self, state):
|
|
|
|
self._state = state
|
2013-01-11 23:00:58 +00:00
|
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
2013-01-06 15:51:04 +00:00
|
|
|
|
|
|
|
def _addLog(self, log):
|
|
|
|
self._log.append(log)
|
|
|
|
self._log = self._log[-300:]
|
2013-01-10 22:40:00 +00:00
|
|
|
self._stateMonitor.addLog(log)
|
2013-01-06 15:51:04 +00:00
|
|
|
|
|
|
|
def _addMessage(self, message):
|
|
|
|
self._messages.append(message)
|
|
|
|
self._messages = self._messages[-300:]
|
2013-01-10 22:40:00 +00:00
|
|
|
self._stateMonitor.addMessage(message)
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
def _setProgressData(self, progress, currentLine, printTime, printTimeLeft):
|
2013-01-06 15:51:04 +00:00
|
|
|
self._progress = progress
|
|
|
|
self._printTime = printTime
|
|
|
|
self._printTimeLeft = printTimeLeft
|
2013-01-11 23:00:58 +00:00
|
|
|
|
|
|
|
formattedPrintTime = None
|
|
|
|
if (self._printTime):
|
2013-01-30 19:56:17 +00:00
|
|
|
formattedPrintTime = util.getFormattedTimeDelta(datetime.timedelta(seconds=self._printTime))
|
2013-01-11 23:00:58 +00:00
|
|
|
|
|
|
|
formattedPrintTimeLeft = None
|
|
|
|
if (self._printTimeLeft):
|
2013-01-30 19:56:17 +00:00
|
|
|
formattedPrintTimeLeft = util.getFormattedTimeDelta(datetime.timedelta(minutes=self._printTimeLeft))
|
2013-01-11 23:00:58 +00:00
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
self._stateMonitor.setProgress({"progress": self._progress, "currentLine": currentLine, "printTime": formattedPrintTime, "printTimeLeft": formattedPrintTimeLeft})
|
2013-01-06 15:51:04 +00:00
|
|
|
|
|
|
|
def _addTemperatureData(self, temp, bedTemp, targetTemp, bedTargetTemp):
|
2013-03-03 18:37:52 +00:00
|
|
|
currentTimeUtc = int(time.time() * 1000)
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2013-01-22 16:44:20 +00:00
|
|
|
self._temps["actual"].append((currentTimeUtc, temp))
|
2013-01-06 15:51:04 +00:00
|
|
|
self._temps["actual"] = self._temps["actual"][-300:]
|
|
|
|
|
2013-01-22 16:44:20 +00:00
|
|
|
self._temps["target"].append((currentTimeUtc, targetTemp))
|
2013-01-06 15:51:04 +00:00
|
|
|
self._temps["target"] = self._temps["target"][-300:]
|
|
|
|
|
2013-01-22 16:44:20 +00:00
|
|
|
self._temps["actualBed"].append((currentTimeUtc, bedTemp))
|
2013-01-06 15:51:04 +00:00
|
|
|
self._temps["actualBed"] = self._temps["actualBed"][-300:]
|
|
|
|
|
2013-01-22 16:44:20 +00:00
|
|
|
self._temps["targetBed"].append((currentTimeUtc, bedTargetTemp))
|
2013-01-06 15:51:04 +00:00
|
|
|
self._temps["targetBed"] = self._temps["targetBed"][-300:]
|
|
|
|
|
|
|
|
self._temp = temp
|
|
|
|
self._bedTemp = bedTemp
|
|
|
|
self._targetTemp = targetTemp
|
|
|
|
self._targetBedTemp = bedTargetTemp
|
|
|
|
|
2013-01-22 16:44:20 +00:00
|
|
|
self._stateMonitor.addTemperature({"currentTime": currentTimeUtc, "temp": self._temp, "bedTemp": self._bedTemp, "targetTemp": self._targetTemp, "targetBedTemp": self._targetBedTemp})
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-02-03 20:14:22 +00:00
|
|
|
def _setJobData(self, filename, gcodeList):
|
2013-01-06 15:51:04 +00:00
|
|
|
self._filename = filename
|
|
|
|
self._gcodeList = gcodeList
|
|
|
|
|
2013-01-06 20:19:39 +00:00
|
|
|
lines = None
|
|
|
|
if self._gcodeList:
|
|
|
|
lines = len(self._gcodeList)
|
|
|
|
|
2013-01-11 23:00:58 +00:00
|
|
|
formattedFilename = None
|
2013-02-03 20:14:22 +00:00
|
|
|
estimatedPrintTime = None
|
|
|
|
filament = None
|
2013-01-11 23:00:58 +00:00
|
|
|
if self._filename:
|
|
|
|
formattedFilename = os.path.basename(self._filename)
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-02-03 20:14:22 +00:00
|
|
|
fileData = self._gcodeManager.getFileData(filename)
|
|
|
|
if fileData is not None and "gcodeAnalysis" in fileData.keys():
|
|
|
|
if "estimatedPrintTime" in fileData["gcodeAnalysis"].keys():
|
|
|
|
estimatedPrintTime = fileData["gcodeAnalysis"]["estimatedPrintTime"]
|
|
|
|
if "filament" in fileData["gcodeAnalysis"].keys():
|
|
|
|
filament = fileData["gcodeAnalysis"]["filament"]
|
|
|
|
|
|
|
|
self._stateMonitor.setJobData({"filename": formattedFilename, "lines": lines, "estimatedPrintTime": estimatedPrintTime, "filament": filament})
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-01-11 23:00:58 +00:00
|
|
|
def _sendInitialStateUpdate(self, callback):
|
2013-01-06 15:51:04 +00:00
|
|
|
try:
|
2013-01-10 22:40:00 +00:00
|
|
|
data = self._stateMonitor.getCurrentData()
|
|
|
|
data.update({
|
|
|
|
"temperatureHistory": self._temps,
|
|
|
|
"logHistory": self._log,
|
|
|
|
"messageHistory": self._messages
|
|
|
|
})
|
|
|
|
callback.sendHistoryData(data)
|
2013-01-06 15:51:04 +00:00
|
|
|
except Exception, err:
|
|
|
|
import sys
|
|
|
|
sys.stderr.write("ERROR: %s\n" % str(err))
|
|
|
|
pass
|
|
|
|
|
|
|
|
def _getStateFlags(self):
|
2013-05-26 16:53:43 +00:00
|
|
|
if not settings().getBoolean(["feature", "sdSupport"]) or self._comm is None:
|
|
|
|
sdReady = False
|
|
|
|
else:
|
|
|
|
sdReady = self._comm.isSdReady()
|
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
return {
|
|
|
|
"operational": self.isOperational(),
|
|
|
|
"printing": self.isPrinting(),
|
|
|
|
"closedOrError": self.isClosedOrError(),
|
|
|
|
"error": self.isError(),
|
|
|
|
"loading": self.isLoading(),
|
|
|
|
"paused": self.isPaused(),
|
2013-05-26 16:53:43 +00:00
|
|
|
"ready": self.isReady(),
|
|
|
|
"sdReady": sdReady
|
2013-01-06 15:51:04 +00:00
|
|
|
}
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
#~~ callbacks triggered from self._comm
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
def mcLog(self, message):
|
|
|
|
"""
|
|
|
|
Callback method for the comm object, called upon log output.
|
|
|
|
"""
|
|
|
|
self._addLog(message)
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
|
|
|
|
self._addTemperatureData(temp, bedTemp, targetTemp, bedTargetTemp)
|
2012-12-25 10:55:00 +00:00
|
|
|
|
|
|
|
def mcStateChange(self, state):
|
2012-12-28 19:37:40 +00:00
|
|
|
"""
|
|
|
|
Callback method for the comm object, called if the connection state changes.
|
|
|
|
"""
|
2013-01-06 15:51:04 +00:00
|
|
|
oldState = self._state
|
|
|
|
|
2013-02-03 20:14:22 +00:00
|
|
|
# forward relevant state changes to timelapse
|
2013-01-06 15:51:04 +00:00
|
|
|
if self._timelapse is not None:
|
2013-01-30 19:56:17 +00:00
|
|
|
if oldState == self._comm.STATE_PRINTING and state != self._comm.STATE_PAUSED:
|
2013-01-06 15:51:04 +00:00
|
|
|
self._timelapse.onPrintjobStopped()
|
2013-01-30 19:56:17 +00:00
|
|
|
elif state == self._comm.STATE_PRINTING and oldState != self._comm.STATE_PAUSED:
|
2013-01-06 15:51:04 +00:00
|
|
|
self._timelapse.onPrintjobStarted(self._filename)
|
|
|
|
|
2013-02-03 20:14:22 +00:00
|
|
|
# forward relevant state changes to gcode manager
|
2013-02-03 21:34:52 +00:00
|
|
|
if self._comm is not None and oldState == self._comm.STATE_PRINTING:
|
2013-01-30 19:56:17 +00:00
|
|
|
if state == self._comm.STATE_OPERATIONAL:
|
|
|
|
self._gcodeManager.printSucceeded(self._filename)
|
|
|
|
elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR:
|
|
|
|
self._gcodeManager.printFailed(self._filename)
|
2013-05-20 17:18:03 +00:00
|
|
|
self._gcodeManager.resumeAnalysis() # printing done, put those cpu cycles to good use
|
2013-02-13 15:19:18 +00:00
|
|
|
elif self._comm is not None and state == self._comm.STATE_PRINTING:
|
2013-05-20 17:18:03 +00:00
|
|
|
self._gcodeManager.pauseAnalysis() # do not analyse gcode while printing
|
2013-01-30 19:56:17 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
self._setState(state)
|
2012-12-25 10:55:00 +00:00
|
|
|
|
|
|
|
def mcMessage(self, message):
|
2012-12-28 19:37:40 +00:00
|
|
|
"""
|
|
|
|
Callback method for the comm object, called upon message exchanges via serial.
|
|
|
|
Stores the message in the message buffer, truncates buffer to the last 300 lines.
|
|
|
|
"""
|
2013-01-06 15:51:04 +00:00
|
|
|
self._addMessage(message)
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
def mcProgress(self):
|
2012-12-28 19:37:40 +00:00
|
|
|
"""
|
|
|
|
Callback method for the comm object, called upon any change in progress of the printjob.
|
|
|
|
Triggers storage of new values for printTime, printTimeLeft and the current line.
|
|
|
|
"""
|
2013-01-06 15:51:04 +00:00
|
|
|
oldProgress = self._progress
|
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
if self._sdPrinting:
|
|
|
|
newLine = None
|
|
|
|
(filePos, fileSize) = self._comm.getSdProgress()
|
2013-05-22 16:32:19 +00:00
|
|
|
if fileSize > 0:
|
|
|
|
newProgress = float(filePos) / float(fileSize)
|
|
|
|
else:
|
|
|
|
newProgress = 0.0
|
2013-05-20 17:18:03 +00:00
|
|
|
else:
|
|
|
|
newLine = self._comm.getPrintPos()
|
2013-05-22 16:56:02 +00:00
|
|
|
if self._gcodeList is not None:
|
|
|
|
newProgress = float(newLine) / float(len(self._gcodeList))
|
|
|
|
else:
|
|
|
|
newProgress = 0.0
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
self._setProgressData(newProgress, newLine, self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2012-12-25 10:55:00 +00:00
|
|
|
def mcZChange(self, newZ):
|
2012-12-28 19:37:40 +00:00
|
|
|
"""
|
|
|
|
Callback method for the comm object, called upon change of the z-layer.
|
|
|
|
"""
|
2013-01-06 16:11:32 +00:00
|
|
|
oldZ = self._currentZ
|
2013-01-06 15:51:04 +00:00
|
|
|
if self._timelapse is not None:
|
|
|
|
self._timelapse.onZChange(oldZ, newZ)
|
|
|
|
|
|
|
|
self._setCurrentZ(newZ)
|
|
|
|
|
2013-05-26 16:53:43 +00:00
|
|
|
def mcSdStateChange(self, sdReady):
|
|
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
def mcSdFiles(self, files):
|
|
|
|
self._sendTriggerUpdateCallbacks("gcodeFiles")
|
|
|
|
|
|
|
|
def mcSdSelected(self, filename, filesize):
|
|
|
|
self._sdFile = filename
|
|
|
|
|
|
|
|
self._setJobData(filename, None)
|
|
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
|
|
|
|
|
|
|
if self._sdPrintAfterSelect:
|
|
|
|
self.startPrint()
|
|
|
|
|
|
|
|
def mcSdPrintingDone(self):
|
|
|
|
self._sdPrinting = False
|
|
|
|
self._setProgressData(1.0, None, self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
|
|
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
|
|
|
|
|
|
|
#~~ sd file handling
|
|
|
|
|
|
|
|
def getSdFiles(self):
|
|
|
|
if self._comm is None:
|
|
|
|
return
|
|
|
|
return self._comm.getSdFiles()
|
|
|
|
|
|
|
|
def addSdFile(self, filename, file):
|
|
|
|
if not self._comm:
|
|
|
|
return
|
|
|
|
|
|
|
|
self._sdStreamer = SdFileStreamer(self._comm, filename, file, self._onSdFileStreamProgress, self._onSdFileStreamFinish)
|
|
|
|
self._sdStreamer.start()
|
|
|
|
|
|
|
|
def deleteSdFile(self, filename):
|
|
|
|
if not self._comm:
|
|
|
|
return
|
|
|
|
|
|
|
|
if self._sdFile == filename:
|
|
|
|
self._sdFile = None
|
|
|
|
self._comm.deleteSdFile(filename)
|
|
|
|
|
|
|
|
def selectSdFile(self, filename, printAfterSelect):
|
|
|
|
if not self._comm:
|
|
|
|
return
|
|
|
|
|
|
|
|
self._sdPrintAfterSelect = printAfterSelect
|
|
|
|
self._comm.selectSdFile(filename)
|
|
|
|
|
2013-05-26 16:53:43 +00:00
|
|
|
def initSdCard(self):
|
|
|
|
if not self._comm:
|
|
|
|
return
|
|
|
|
self._comm.initSdCard()
|
|
|
|
|
|
|
|
def releaseSdCard(self):
|
|
|
|
if not self._comm:
|
|
|
|
return
|
|
|
|
self._comm.releaseSdCard()
|
|
|
|
|
|
|
|
def refreshSdFiles(self):
|
|
|
|
if not self._comm:
|
|
|
|
return
|
|
|
|
self._comm.refreshSdFiles()
|
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
#~~ callbacks triggered by sdFileStreamer
|
|
|
|
|
|
|
|
def _onSdFileStreamProgress(self, filename, progress):
|
|
|
|
self._stateMonitor.setSdUploadData({"filename": filename, "progress": progress})
|
|
|
|
|
|
|
|
def _onSdFileStreamFinish(self, filename):
|
|
|
|
self._setCurrentZ(None)
|
|
|
|
self._setProgressData(None, None, None, None)
|
|
|
|
self._sdStreamer = None
|
|
|
|
|
|
|
|
self._stateMonitor.setSdUploadData({"filename": None, "progress": None})
|
|
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
#~~ callbacks triggered by gcodeLoader
|
|
|
|
|
2013-01-12 23:58:54 +00:00
|
|
|
def _onGcodeLoadingProgress(self, filename, progress, mode):
|
2013-01-11 23:00:58 +00:00
|
|
|
formattedFilename = None
|
|
|
|
if filename is not None:
|
|
|
|
formattedFilename = os.path.basename(filename)
|
|
|
|
|
2013-01-12 23:58:54 +00:00
|
|
|
self._stateMonitor.setGcodeData({"filename": formattedFilename, "progress": progress, "mode": mode})
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-02-03 20:14:22 +00:00
|
|
|
def _onGcodeLoaded(self, filename, gcodeList):
|
|
|
|
self._setJobData(filename, gcodeList)
|
2013-01-06 15:51:04 +00:00
|
|
|
self._setCurrentZ(None)
|
2013-05-20 17:18:03 +00:00
|
|
|
self._setProgressData(None, None, None, None)
|
2013-01-06 15:51:04 +00:00
|
|
|
self._gcodeLoader = None
|
|
|
|
|
2013-01-12 00:00:55 +00:00
|
|
|
self._stateMonitor.setGcodeData({"filename": None, "progress": None})
|
2013-01-11 23:00:58 +00:00
|
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-03-26 05:09:36 +00:00
|
|
|
def _onGcodeLoadedToPrint(self, filename, gcodeList):
|
2013-03-30 17:21:49 +00:00
|
|
|
self._onGcodeLoaded(filename, gcodeList)
|
|
|
|
self.startPrint()
|
2013-03-26 05:09:36 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
#~~ state reports
|
|
|
|
|
2012-12-29 14:41:23 +00:00
|
|
|
def feedrateState(self):
|
2013-01-06 15:51:04 +00:00
|
|
|
if self._comm is not None:
|
|
|
|
feedrateModifiers = self._comm.getFeedrateModifiers()
|
2012-12-29 14:41:23 +00:00
|
|
|
result = {}
|
2013-01-06 15:51:04 +00:00
|
|
|
for structure in self._feedrateModifierMapping.keys():
|
|
|
|
if (feedrateModifiers.has_key(self._feedrateModifierMapping[structure])):
|
|
|
|
result[structure] = int(round(feedrateModifiers[self._feedrateModifierMapping[structure]] * 100))
|
2012-12-29 14:41:23 +00:00
|
|
|
else:
|
|
|
|
result[structure] = 100
|
|
|
|
return result
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2012-12-25 10:55:00 +00:00
|
|
|
def getStateString(self):
|
2012-12-28 19:37:40 +00:00
|
|
|
"""
|
|
|
|
Returns a human readable string corresponding to the current communication state.
|
|
|
|
"""
|
2013-01-06 15:51:04 +00:00
|
|
|
if self._comm is None:
|
2012-12-28 19:37:40 +00:00
|
|
|
return "Offline"
|
2012-12-25 10:55:00 +00:00
|
|
|
else:
|
2013-01-06 15:51:04 +00:00
|
|
|
return self._comm.getStateString()
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2013-05-31 21:24:21 +00:00
|
|
|
def getCurrentData(self):
|
|
|
|
return self._stateMonitor.getCurrentData()
|
|
|
|
|
|
|
|
def getCurrentTemperatures(self):
|
|
|
|
return {
|
|
|
|
"extruder": {
|
|
|
|
"current": self._temp,
|
|
|
|
"target": self._targetTemp
|
|
|
|
},
|
|
|
|
"bed": {
|
|
|
|
"current": self._bedTemp,
|
|
|
|
"target": self._targetBedTemp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-25 10:55:00 +00:00
|
|
|
def isClosedOrError(self):
|
2013-01-06 15:51:04 +00:00
|
|
|
return self._comm is None or self._comm.isClosedOrError()
|
2012-12-25 10:55:00 +00:00
|
|
|
|
|
|
|
def isOperational(self):
|
2013-01-06 15:51:04 +00:00
|
|
|
return self._comm is not None and self._comm.isOperational()
|
2012-12-25 10:55:00 +00:00
|
|
|
|
2012-12-26 14:03:34 +00:00
|
|
|
def isPrinting(self):
|
2013-01-06 15:51:04 +00:00
|
|
|
return self._comm is not None and self._comm.isPrinting()
|
2012-12-26 14:03:34 +00:00
|
|
|
|
|
|
|
def isPaused(self):
|
2013-01-06 15:51:04 +00:00
|
|
|
return self._comm is not None and self._comm.isPaused()
|
2012-12-26 14:03:34 +00:00
|
|
|
|
|
|
|
def isError(self):
|
2013-01-06 15:51:04 +00:00
|
|
|
return self._comm is not None and self._comm.isError()
|
2012-12-26 14:03:34 +00:00
|
|
|
|
|
|
|
def isReady(self):
|
2013-05-20 17:18:03 +00:00
|
|
|
return self._gcodeLoader is None and self._sdStreamer is None and ((self._gcodeList and len(self._gcodeList) > 0) or self._sdFile)
|
2012-12-28 19:37:40 +00:00
|
|
|
|
|
|
|
def isLoading(self):
|
2013-05-20 17:18:03 +00:00
|
|
|
return self._gcodeLoader is not None or self._sdStreamer is not None
|
2012-12-28 19:37:40 +00:00
|
|
|
|
2013-01-11 23:00:58 +00:00
|
|
|
class GcodeLoader(threading.Thread):
|
2012-12-28 19:37:40 +00:00
|
|
|
"""
|
|
|
|
The GcodeLoader takes care of loading a gcode-File from disk and parsing it into a gcode object in a separate
|
|
|
|
thread while constantly notifying interested listeners about the current progress.
|
|
|
|
The progress is returned as a float value between 0 and 1 which is to be interpreted as the percentage of completion.
|
|
|
|
"""
|
|
|
|
|
2013-01-11 23:00:58 +00:00
|
|
|
def __init__(self, filename, progressCallback, loadedCallback):
|
|
|
|
threading.Thread.__init__(self)
|
2012-12-28 19:37:40 +00:00
|
|
|
|
2013-01-11 23:00:58 +00:00
|
|
|
self._progressCallback = progressCallback
|
|
|
|
self._loadedCallback = loadedCallback
|
2012-12-28 19:37:40 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
self._filename = filename
|
|
|
|
self._gcodeList = None
|
2012-12-28 19:37:40 +00:00
|
|
|
|
|
|
|
def run(self):
|
|
|
|
#Send an initial M110 to reset the line counter to zero.
|
|
|
|
prevLineType = lineType = "CUSTOM"
|
2013-03-16 17:25:39 +00:00
|
|
|
gcodeList = ["M110 N0"]
|
2013-01-12 23:58:54 +00:00
|
|
|
filesize = os.stat(self._filename).st_size
|
2013-01-06 15:51:04 +00:00
|
|
|
with open(self._filename, "r") as file:
|
2012-12-28 19:37:40 +00:00
|
|
|
for line in file:
|
|
|
|
if line.startswith(";TYPE:"):
|
|
|
|
lineType = line[6:].strip()
|
|
|
|
if ";" in line:
|
|
|
|
line = line[0:line.find(";")]
|
|
|
|
line = line.strip()
|
|
|
|
if len(line) > 0:
|
|
|
|
if prevLineType != lineType:
|
|
|
|
gcodeList.append((line, lineType, ))
|
|
|
|
else:
|
|
|
|
gcodeList.append(line)
|
|
|
|
prevLineType = lineType
|
2013-01-12 23:58:54 +00:00
|
|
|
self._onLoadingProgress(float(file.tell()) / float(filesize))
|
2012-12-28 19:37:40 +00:00
|
|
|
|
2013-01-06 15:51:04 +00:00
|
|
|
self._gcodeList = gcodeList
|
2013-02-03 20:14:22 +00:00
|
|
|
self._loadedCallback(self._filename, self._gcodeList)
|
2012-12-28 19:37:40 +00:00
|
|
|
|
2013-01-12 23:58:54 +00:00
|
|
|
def _onLoadingProgress(self, progress):
|
|
|
|
self._progressCallback(self._filename, progress, "loading")
|
|
|
|
|
|
|
|
def _onParsingProgress(self, progress):
|
|
|
|
self._progressCallback(self._filename, progress, "parsing")
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
class SdFileStreamer(threading.Thread):
|
|
|
|
def __init__(self, comm, filename, file, progressCallback, finishCallback):
|
|
|
|
threading.Thread.__init__(self)
|
|
|
|
|
|
|
|
self._comm = comm
|
|
|
|
self._filename = filename
|
|
|
|
self._file = file
|
|
|
|
self._progressCallback = progressCallback
|
|
|
|
self._finishCallback = finishCallback
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
if self._comm.isBusy():
|
|
|
|
return
|
|
|
|
|
|
|
|
name = self._filename[:self._filename.rfind(".")]
|
|
|
|
sdFilename = name[:8] + ".GCO"
|
|
|
|
try:
|
|
|
|
size = os.stat(self._file).st_size
|
|
|
|
with open(self._file, "r") as f:
|
|
|
|
self._comm.startSdFileTransfer(sdFilename)
|
|
|
|
for line in f:
|
2013-05-21 21:30:29 +00:00
|
|
|
if ";" in line:
|
|
|
|
line = line[0:line.find(";")]
|
|
|
|
line = line.strip()
|
|
|
|
if len(line) > 0:
|
|
|
|
self._comm.sendCommand(line)
|
|
|
|
time.sleep(0.001) # do not send too fast
|
2013-05-20 17:18:03 +00:00
|
|
|
self._progressCallback(sdFilename, float(f.tell()) / float(size))
|
|
|
|
finally:
|
|
|
|
self._comm.endSdFileTransfer(sdFilename)
|
|
|
|
self._finishCallback(sdFilename)
|
|
|
|
|
2013-01-10 22:40:00 +00:00
|
|
|
class StateMonitor(object):
|
2013-01-11 23:00:58 +00:00
|
|
|
def __init__(self, ratelimit, updateCallback, addTemperatureCallback, addLogCallback, addMessageCallback):
|
2013-01-10 22:40:00 +00:00
|
|
|
self._ratelimit = ratelimit
|
|
|
|
self._updateCallback = updateCallback
|
2013-01-11 23:00:58 +00:00
|
|
|
self._addTemperatureCallback = addTemperatureCallback
|
|
|
|
self._addLogCallback = addLogCallback
|
|
|
|
self._addMessageCallback = addMessageCallback
|
2013-01-06 15:51:04 +00:00
|
|
|
|
2013-01-10 22:40:00 +00:00
|
|
|
self._state = None
|
|
|
|
self._jobData = None
|
|
|
|
self._gcodeData = None
|
2013-05-20 17:18:03 +00:00
|
|
|
self._sdUploadData = None
|
2013-01-10 22:40:00 +00:00
|
|
|
self._currentZ = None
|
|
|
|
self._progress = None
|
|
|
|
|
2013-01-11 23:00:58 +00:00
|
|
|
self._changeEvent = threading.Event()
|
2013-01-10 22:40:00 +00:00
|
|
|
|
|
|
|
self._lastUpdate = time.time()
|
2013-01-11 23:00:58 +00:00
|
|
|
self._worker = threading.Thread(target=self._work)
|
2013-01-13 17:29:11 +00:00
|
|
|
self._worker.daemon = True
|
2013-01-10 22:40:00 +00:00
|
|
|
self._worker.start()
|
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
def reset(self, state=None, jobData=None, gcodeData=None, sdUploadData=None, progress=None, currentZ=None):
|
2013-01-10 22:40:00 +00:00
|
|
|
self.setState(state)
|
2013-01-11 23:00:58 +00:00
|
|
|
self.setJobData(jobData)
|
|
|
|
self.setGcodeData(gcodeData)
|
2013-05-20 17:18:03 +00:00
|
|
|
self.setSdUploadData(sdUploadData)
|
2013-01-11 23:00:58 +00:00
|
|
|
self.setProgress(progress)
|
|
|
|
self.setCurrentZ(currentZ)
|
2013-01-10 22:40:00 +00:00
|
|
|
|
|
|
|
def addTemperature(self, temperature):
|
2013-01-11 23:00:58 +00:00
|
|
|
self._addTemperatureCallback(temperature)
|
2013-01-10 22:40:00 +00:00
|
|
|
self._changeEvent.set()
|
|
|
|
|
|
|
|
def addLog(self, log):
|
2013-01-11 23:00:58 +00:00
|
|
|
self._addLogCallback(log)
|
2013-01-10 22:40:00 +00:00
|
|
|
self._changeEvent.set()
|
|
|
|
|
|
|
|
def addMessage(self, message):
|
2013-01-11 23:00:58 +00:00
|
|
|
self._addMessageCallback(message)
|
2013-01-10 22:40:00 +00:00
|
|
|
self._changeEvent.set()
|
|
|
|
|
|
|
|
def setCurrentZ(self, currentZ):
|
|
|
|
self._currentZ = currentZ
|
|
|
|
self._changeEvent.set()
|
2013-01-10 20:02:47 +00:00
|
|
|
|
2013-01-10 22:40:00 +00:00
|
|
|
def setState(self, state):
|
|
|
|
self._state = state
|
|
|
|
self._changeEvent.set()
|
2013-01-10 20:02:47 +00:00
|
|
|
|
2013-01-10 22:40:00 +00:00
|
|
|
def setJobData(self, jobData):
|
|
|
|
self._jobData = jobData
|
|
|
|
self._changeEvent.set()
|
2013-01-10 20:02:47 +00:00
|
|
|
|
2013-01-10 22:40:00 +00:00
|
|
|
def setGcodeData(self, gcodeData):
|
|
|
|
self._gcodeData = gcodeData
|
|
|
|
self._changeEvent.set()
|
2013-01-10 20:02:47 +00:00
|
|
|
|
2013-05-20 17:18:03 +00:00
|
|
|
def setSdUploadData(self, uploadData):
|
|
|
|
self._sdUploadData = uploadData
|
|
|
|
self._changeEvent.set()
|
|
|
|
|
2013-01-10 22:40:00 +00:00
|
|
|
def setProgress(self, progress):
|
|
|
|
self._progress = progress
|
|
|
|
self._changeEvent.set()
|
2013-01-10 20:02:47 +00:00
|
|
|
|
2013-01-10 22:40:00 +00:00
|
|
|
def _work(self):
|
|
|
|
while True:
|
|
|
|
self._changeEvent.wait()
|
2013-01-11 23:00:58 +00:00
|
|
|
|
|
|
|
now = time.time()
|
|
|
|
delta = now - self._lastUpdate
|
|
|
|
additionalWaitTime = self._ratelimit - delta
|
2013-01-10 22:40:00 +00:00
|
|
|
if additionalWaitTime > 0:
|
|
|
|
time.sleep(additionalWaitTime)
|
|
|
|
|
|
|
|
data = self.getCurrentData()
|
|
|
|
self._updateCallback(data)
|
|
|
|
self._lastUpdate = time.time()
|
|
|
|
self._changeEvent.clear()
|
|
|
|
|
|
|
|
def getCurrentData(self):
|
|
|
|
return {
|
|
|
|
"state": self._state,
|
|
|
|
"job": self._jobData,
|
|
|
|
"gcode": self._gcodeData,
|
2013-05-20 17:18:03 +00:00
|
|
|
"sdUpload": self._sdUploadData,
|
2013-01-11 23:00:58 +00:00
|
|
|
"currentZ": self._currentZ,
|
|
|
|
"progress": self._progress
|
2013-01-10 22:40:00 +00:00
|
|
|
}
|
2013-01-11 23:00:58 +00:00
|
|
|
|