Slicers might raise the print head first for a "nose dive" onto the print platform, leading to a high peakZ already dialed in, with no way to get it down again. This way events won't be fired until the print reaches the height of the initial starting point. As a z-change is a z-change if z changes, we'll just fire the event now (if oldZ != newZ). Event consumers will have to think of a way to filter out the noise.
668 lines
19 KiB
Python
668 lines
19 KiB
Python
# coding=utf-8
|
|
__author__ = "Gina Häußge <osd@foosel.net>"
|
|
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
|
|
|
import time
|
|
import datetime
|
|
import threading
|
|
import copy
|
|
import os
|
|
|
|
#import logging, logging.config
|
|
|
|
import octoprint.util.comm as comm
|
|
import octoprint.util as util
|
|
|
|
from octoprint.settings import settings
|
|
from octoprint.events import eventManager
|
|
|
|
def getConnectionOptions():
|
|
"""
|
|
Retrieves the available ports, baudrates, prefered port and baudrate for connecting to the printer.
|
|
"""
|
|
return {
|
|
"ports": comm.serialList(),
|
|
"baudrates": comm.baudrateList(),
|
|
"portPreference": settings().get(["serial", "port"]),
|
|
"baudratePreference": settings().getInt(["serial", "baudrate"]),
|
|
"autoconnect": settings().getBoolean(["serial", "autoconnect"])
|
|
}
|
|
|
|
class Printer():
|
|
def __init__(self, gcodeManager):
|
|
from collections import deque
|
|
|
|
self._gcodeManager = gcodeManager
|
|
|
|
# state
|
|
self._temp = None
|
|
self._bedTemp = None
|
|
self._targetTemp = None
|
|
self._targetBedTemp = None
|
|
self._temps = {
|
|
"actual": deque([], 300),
|
|
"target": deque([], 300),
|
|
"actualBed": deque([], 300),
|
|
"targetBed": deque([], 300)
|
|
}
|
|
self._tempBacklog = []
|
|
|
|
self._latestMessage = None
|
|
self._messages = deque([], 300)
|
|
self._messageBacklog = []
|
|
|
|
self._latestLog = None
|
|
self._log = deque([], 300)
|
|
self._logBacklog = []
|
|
|
|
self._state = None
|
|
|
|
self._currentZ = None
|
|
|
|
self._progress = None
|
|
self._printTime = None
|
|
self._printTimeLeft = None
|
|
|
|
self._printAfterSelect = False
|
|
|
|
# sd handling
|
|
self._sdPrinting = False
|
|
self._sdStreaming = False
|
|
|
|
self._selectedFile = None
|
|
|
|
# comm
|
|
self._comm = None
|
|
|
|
# callbacks
|
|
self._callbacks = []
|
|
self._lastProgressReport = None
|
|
|
|
self._stateMonitor = StateMonitor(
|
|
ratelimit=0.5,
|
|
updateCallback=self._sendCurrentDataCallbacks,
|
|
addTemperatureCallback=self._sendAddTemperatureCallbacks,
|
|
addLogCallback=self._sendAddLogCallbacks,
|
|
addMessageCallback=self._sendAddMessageCallbacks
|
|
)
|
|
self._stateMonitor.reset(
|
|
state={"state": None, "stateString": self.getStateString(), "flags": self._getStateFlags()},
|
|
jobData={"filename": None, "filesize": None, "estimatedPrintTime": None, "filament": None},
|
|
progress={"progress": None, "filepos": None, "printTime": None, "printTimeLeft": None},
|
|
currentZ=None
|
|
)
|
|
|
|
#~~ callback handling
|
|
|
|
def registerCallback(self, callback):
|
|
self._callbacks.append(callback)
|
|
self._sendInitialStateUpdate(callback)
|
|
|
|
def unregisterCallback(self, callback):
|
|
if callback in self._callbacks:
|
|
self._callbacks.remove(callback)
|
|
|
|
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
|
|
|
|
def _sendCurrentDataCallbacks(self, data):
|
|
for callback in self._callbacks:
|
|
try: callback.sendCurrentData(copy.deepcopy(data))
|
|
except: pass
|
|
|
|
def _sendTriggerUpdateCallbacks(self, type):
|
|
for callback in self._callbacks:
|
|
try: callback.sendUpdateTrigger(type)
|
|
except: pass
|
|
|
|
def _sendFeedbackCommandOutput(self, name, output):
|
|
for callback in self._callbacks:
|
|
try: callback.sendFeedbackCommandOutput(name, output)
|
|
except: pass
|
|
|
|
#~~ printer commands
|
|
|
|
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.
|
|
"""
|
|
if self._comm is not None:
|
|
self._comm.close()
|
|
self._comm = comm.MachineCom(port, baudrate, callbackObject=self)
|
|
|
|
def disconnect(self):
|
|
"""
|
|
Closes the connection to the printer.
|
|
"""
|
|
if self._comm is not None:
|
|
self._comm.close()
|
|
self._comm = None
|
|
eventManager().fire("Disconnected")
|
|
|
|
def command(self, command):
|
|
"""
|
|
Sends a single gcode command to the printer.
|
|
"""
|
|
self.commands([command])
|
|
|
|
def commands(self, commands):
|
|
"""
|
|
Sends multiple gcode commands (provided as a list) to the printer.
|
|
"""
|
|
for command in commands:
|
|
self._comm.sendCommand(command)
|
|
|
|
def selectFile(self, filename, sd, printAfterSelect=False):
|
|
if self._comm is not None and (self._comm.isBusy() or self._comm.isStreaming()):
|
|
return
|
|
|
|
self._printAfterSelect = printAfterSelect
|
|
self._comm.selectFile(filename, sd)
|
|
|
|
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() or self._comm.isPrinting():
|
|
return
|
|
if self._selectedFile is None:
|
|
return
|
|
|
|
self._setCurrentZ(None)
|
|
self._comm.startPrint()
|
|
|
|
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
|
|
|
|
self._comm.cancelPrint()
|
|
|
|
if disableMotorsAndHeater:
|
|
self.commands(["M84", "M104 S0", "M140 S0", "M106 S0"]) # disable motors, switch off heaters and fan
|
|
|
|
# reset progress, height, print time
|
|
self._setCurrentZ(None)
|
|
self._setProgressData(None, None, None, None)
|
|
|
|
# mark print as failure
|
|
if self._selectedFile is not None:
|
|
self._gcodeManager.printFailed(self._selectedFile["filename"])
|
|
eventManager().fire("PrintFailed", self._selectedFile["filename"])
|
|
|
|
#~~ state monitoring
|
|
|
|
def _setCurrentZ(self, currentZ):
|
|
self._currentZ = currentZ
|
|
|
|
formattedCurrentZ = None
|
|
if self._currentZ:
|
|
formattedCurrentZ = "%.2f mm" % (self._currentZ)
|
|
self._stateMonitor.setCurrentZ(formattedCurrentZ)
|
|
|
|
def _setState(self, state):
|
|
self._state = state
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
|
|
|
def _addLog(self, log):
|
|
self._log.append(log)
|
|
self._stateMonitor.addLog(log)
|
|
|
|
def _addMessage(self, message):
|
|
self._messages.append(message)
|
|
self._stateMonitor.addMessage(message)
|
|
|
|
def _setProgressData(self, progress, filepos, printTime, printTimeLeft):
|
|
self._progress = progress
|
|
self._printTime = printTime
|
|
self._printTimeLeft = printTimeLeft
|
|
|
|
formattedPrintTime = None
|
|
if (self._printTime):
|
|
formattedPrintTime = util.getFormattedTimeDelta(datetime.timedelta(seconds=self._printTime))
|
|
|
|
formattedPrintTimeLeft = None
|
|
if (self._printTimeLeft):
|
|
formattedPrintTimeLeft = util.getFormattedTimeDelta(datetime.timedelta(minutes=self._printTimeLeft))
|
|
|
|
formattedFilePos = None
|
|
if (filepos):
|
|
formattedFilePos = util.getFormattedSize(filepos)
|
|
|
|
self._stateMonitor.setProgress({"progress": self._progress, "filepos": formattedFilePos, "printTime": formattedPrintTime, "printTimeLeft": formattedPrintTimeLeft})
|
|
|
|
def _addTemperatureData(self, temp, bedTemp, targetTemp, bedTargetTemp):
|
|
currentTimeUtc = int(time.time() * 1000)
|
|
|
|
self._temps["actual"].append((currentTimeUtc, temp))
|
|
self._temps["target"].append((currentTimeUtc, targetTemp))
|
|
self._temps["actualBed"].append((currentTimeUtc, bedTemp))
|
|
self._temps["targetBed"].append((currentTimeUtc, bedTargetTemp))
|
|
|
|
self._temp = temp
|
|
self._bedTemp = bedTemp
|
|
self._targetTemp = targetTemp
|
|
self._targetBedTemp = bedTargetTemp
|
|
|
|
self._stateMonitor.addTemperature({"currentTime": currentTimeUtc, "temp": self._temp, "bedTemp": self._bedTemp, "targetTemp": self._targetTemp, "targetBedTemp": self._targetBedTemp})
|
|
|
|
def _setJobData(self, filename, filesize, sd):
|
|
if filename is not None:
|
|
self._selectedFile = {
|
|
"filename": filename,
|
|
"filesize": filesize,
|
|
"sd": sd
|
|
}
|
|
else:
|
|
self._selectedFile = None
|
|
|
|
formattedFilename = None
|
|
formattedFilesize = None
|
|
estimatedPrintTime = None
|
|
filament = None
|
|
if filename:
|
|
formattedFilename = os.path.basename(filename)
|
|
|
|
if filesize:
|
|
formattedFilesize = util.getFormattedSize(filesize)
|
|
|
|
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, "filesize": formattedFilesize, "estimatedPrintTime": estimatedPrintTime, "filament": filament, "sd": sd})
|
|
|
|
def _sendInitialStateUpdate(self, callback):
|
|
try:
|
|
data = self._stateMonitor.getCurrentData()
|
|
data.update({
|
|
"temperatureHistory": list(self._temps),
|
|
"logHistory": list(self._log),
|
|
"messageHistory": list(self._messages)
|
|
})
|
|
callback.sendHistoryData(data)
|
|
except Exception, err:
|
|
import sys
|
|
sys.stderr.write("ERROR: %s\n" % str(err))
|
|
pass
|
|
|
|
def _getStateFlags(self):
|
|
if not settings().getBoolean(["feature", "sdSupport"]) or self._comm is None:
|
|
sdReady = False
|
|
else:
|
|
sdReady = self._comm.isSdReady()
|
|
|
|
return {
|
|
"operational": self.isOperational(),
|
|
"printing": self.isPrinting(),
|
|
"closedOrError": self.isClosedOrError(),
|
|
"error": self.isError(),
|
|
"paused": self.isPaused(),
|
|
"ready": self.isReady(),
|
|
"sdReady": sdReady
|
|
}
|
|
|
|
def getCurrentData(self):
|
|
return self._stateMonitor.getCurrentData()
|
|
|
|
#~~ callbacks triggered from self._comm
|
|
|
|
def mcLog(self, message):
|
|
"""
|
|
Callback method for the comm object, called upon log output.
|
|
"""
|
|
self._addLog(message)
|
|
|
|
def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
|
|
self._addTemperatureData(temp, bedTemp, targetTemp, bedTargetTemp)
|
|
|
|
def mcStateChange(self, state):
|
|
"""
|
|
Callback method for the comm object, called if the connection state changes.
|
|
"""
|
|
oldState = self._state
|
|
|
|
# forward relevant state changes to gcode manager
|
|
if self._comm is not None and oldState == self._comm.STATE_PRINTING:
|
|
if self._selectedFile is not None:
|
|
if state == self._comm.STATE_OPERATIONAL:
|
|
self._gcodeManager.printSucceeded(self._selectedFile["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._selectedFile["filename"])
|
|
self._gcodeManager.resumeAnalysis() # printing done, put those cpu cycles to good use
|
|
elif self._comm is not None and state == self._comm.STATE_PRINTING:
|
|
self._gcodeManager.pauseAnalysis() # do not analyse gcode while printing
|
|
|
|
self._setState(state)
|
|
|
|
def mcMessage(self, message):
|
|
"""
|
|
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.
|
|
"""
|
|
self._addMessage(message)
|
|
|
|
def mcProgress(self):
|
|
"""
|
|
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 progress.
|
|
"""
|
|
|
|
self._setProgressData(self._comm.getPrintProgress(), self._comm.getPrintFilepos(), self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate())
|
|
|
|
def mcZChange(self, newZ):
|
|
"""
|
|
Callback method for the comm object, called upon change of the z-layer.
|
|
"""
|
|
oldZ = self._currentZ
|
|
if newZ != oldZ:
|
|
# we have to react to all z-changes, even those that might "go backward" due to a slicer's retraction or
|
|
# anti-backlash-routines. Event subscribes should individually take care to filter out "wrong" z-changes
|
|
eventManager().fire("ZChange", newZ)
|
|
|
|
self._setCurrentZ(newZ)
|
|
|
|
def mcSdStateChange(self, sdReady):
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
|
|
|
def mcSdFiles(self, files):
|
|
self._sendTriggerUpdateCallbacks("gcodeFiles")
|
|
|
|
def mcFileSelected(self, filename, filesize, sd):
|
|
self._setJobData(filename, filesize, sd)
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
|
|
|
if self._printAfterSelect:
|
|
self.startPrint()
|
|
|
|
def mcPrintjobDone(self):
|
|
self._setProgressData(1.0, self._selectedFile["filesize"], self._comm.getPrintTime(), 0)
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
|
|
|
def mcFileTransferStarted(self, filename, filesize):
|
|
self._sdStreaming = True
|
|
self._selectedFile = {
|
|
"filename": filename,
|
|
"filesize": filesize,
|
|
"sd": True
|
|
}
|
|
|
|
self._setJobData(filename, filesize, True)
|
|
self._setProgressData(0.0, 0, 0, None)
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
|
|
|
def mcFileTransferDone(self):
|
|
self._sdStreaming = False
|
|
self._selectedFile = None
|
|
|
|
self._setCurrentZ(None)
|
|
self._setJobData(None, None, None)
|
|
self._setProgressData(None, None, None, None)
|
|
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
|
|
|
|
def mcReceivedRegisteredMessage(self, command, output):
|
|
self._sendFeedbackCommandOutput(command, output)
|
|
|
|
#~~ sd file handling
|
|
|
|
def getSdFiles(self):
|
|
if self._comm is None:
|
|
return
|
|
return self._comm.getSdFiles()
|
|
|
|
def addSdFile(self, filename, path):
|
|
if not self._comm or self._comm.isBusy():
|
|
return
|
|
self._comm.startFileTransfer(path, filename[:8].lower() + ".gco")
|
|
|
|
def deleteSdFile(self, filename):
|
|
if not self._comm:
|
|
return
|
|
self._comm.deleteSdFile(filename)
|
|
|
|
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()
|
|
|
|
#~~ state reports
|
|
|
|
def getStateString(self):
|
|
"""
|
|
Returns a human readable string corresponding to the current communication state.
|
|
"""
|
|
if self._comm is None:
|
|
return "Offline"
|
|
else:
|
|
return self._comm.getStateString()
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
def isClosedOrError(self):
|
|
return self._comm is None or self._comm.isClosedOrError()
|
|
|
|
def isOperational(self):
|
|
return self._comm is not None and self._comm.isOperational()
|
|
|
|
def isPrinting(self):
|
|
return self._comm is not None and self._comm.isPrinting()
|
|
|
|
def isPaused(self):
|
|
return self._comm is not None and self._comm.isPaused()
|
|
|
|
def isError(self):
|
|
return self._comm is not None and self._comm.isError()
|
|
|
|
def isReady(self):
|
|
return self.isOperational() and not self._comm.isStreaming()
|
|
|
|
def isLoading(self):
|
|
return self._gcodeLoader is not None
|
|
|
|
class GcodeLoader(threading.Thread):
|
|
"""
|
|
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.
|
|
"""
|
|
|
|
def __init__(self, filename, progressCallback, loadedCallback):
|
|
threading.Thread.__init__(self)
|
|
|
|
self._progressCallback = progressCallback
|
|
self._loadedCallback = loadedCallback
|
|
|
|
self._filename = filename
|
|
self._gcodeList = None
|
|
|
|
def run(self):
|
|
#Send an initial M110 to reset the line counter to zero.
|
|
prevLineType = lineType = "CUSTOM"
|
|
gcodeList = ["M110 N0"]
|
|
filesize = os.stat(self._filename).st_size
|
|
with open(self._filename, "r") as file:
|
|
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
|
|
self._onLoadingProgress(float(file.tell()) / float(filesize))
|
|
|
|
self._gcodeList = gcodeList
|
|
self._loadedCallback(self._filename, self._gcodeList)
|
|
|
|
def _onLoadingProgress(self, progress):
|
|
self._progressCallback(self._filename, progress, "loading")
|
|
|
|
def _onParsingProgress(self, progress):
|
|
self._progressCallback(self._filename, progress, "parsing")
|
|
|
|
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].lower() + ".gco"
|
|
try:
|
|
size = os.stat(self._file).st_size
|
|
with open(self._file, "r") as f:
|
|
self._comm.startSdFileTransfer(sdFilename)
|
|
for line in f:
|
|
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
|
|
self._progressCallback(sdFilename, float(f.tell()) / float(size))
|
|
finally:
|
|
self._comm.endSdFileTransfer(sdFilename)
|
|
self._finishCallback(sdFilename)
|
|
|
|
class StateMonitor(object):
|
|
def __init__(self, ratelimit, updateCallback, addTemperatureCallback, addLogCallback, addMessageCallback):
|
|
self._ratelimit = ratelimit
|
|
self._updateCallback = updateCallback
|
|
self._addTemperatureCallback = addTemperatureCallback
|
|
self._addLogCallback = addLogCallback
|
|
self._addMessageCallback = addMessageCallback
|
|
|
|
self._state = None
|
|
self._jobData = None
|
|
self._gcodeData = None
|
|
self._sdUploadData = None
|
|
self._currentZ = None
|
|
self._progress = None
|
|
|
|
self._changeEvent = threading.Event()
|
|
|
|
self._lastUpdate = time.time()
|
|
self._worker = threading.Thread(target=self._work)
|
|
self._worker.daemon = True
|
|
self._worker.start()
|
|
|
|
def reset(self, state=None, jobData=None, progress=None, currentZ=None):
|
|
self.setState(state)
|
|
self.setJobData(jobData)
|
|
self.setProgress(progress)
|
|
self.setCurrentZ(currentZ)
|
|
|
|
def addTemperature(self, temperature):
|
|
self._addTemperatureCallback(temperature)
|
|
self._changeEvent.set()
|
|
|
|
def addLog(self, log):
|
|
self._addLogCallback(log)
|
|
self._changeEvent.set()
|
|
|
|
def addMessage(self, message):
|
|
self._addMessageCallback(message)
|
|
self._changeEvent.set()
|
|
|
|
def setCurrentZ(self, currentZ):
|
|
self._currentZ = currentZ
|
|
self._changeEvent.set()
|
|
|
|
def setState(self, state):
|
|
self._state = state
|
|
self._changeEvent.set()
|
|
|
|
def setJobData(self, jobData):
|
|
self._jobData = jobData
|
|
self._changeEvent.set()
|
|
|
|
def setProgress(self, progress):
|
|
self._progress = progress
|
|
self._changeEvent.set()
|
|
|
|
def _work(self):
|
|
while True:
|
|
self._changeEvent.wait()
|
|
|
|
now = time.time()
|
|
delta = now - self._lastUpdate
|
|
additionalWaitTime = self._ratelimit - delta
|
|
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,
|
|
"currentZ": self._currentZ,
|
|
"progress": self._progress
|
|
}
|
|
|