Pauses in the print job are now cleaned from the basis for print time estimation, also switched to a rolling window approach for determining when estimation becomes stable
This commit is contained in:
parent
12a3e659b1
commit
5ac908afd4
3 changed files with 62 additions and 36 deletions
|
|
@ -377,24 +377,31 @@ class Printer():
|
|||
|
||||
def _estimateTotalPrintTime(self, progress, printTime):
|
||||
if not progress or not printTime:
|
||||
#self._estimationLogger.info("{progress};{printTime};;;".format(**locals()))
|
||||
return None
|
||||
|
||||
else:
|
||||
newEstimate = printTime / progress
|
||||
|
||||
if self._timeEstimationData.is_stable():
|
||||
#self._estimationLogger.info("{progress};{printTime};{newEstimate};;".format(**locals()))
|
||||
return newEstimate
|
||||
|
||||
self._timeEstimationData.update(newEstimate)
|
||||
|
||||
#averageTotal = self._timeEstimationData.average_total
|
||||
#averageDistance = self._timeEstimationData.average_distance
|
||||
#
|
||||
#self._estimationLogger.info("{progress};{printTime};{newEstimate};{averageTotal};{averageDistance}".format(**locals()))
|
||||
|
||||
return None
|
||||
|
||||
def _setProgressData(self, progress, filepos, printTime, printTimeLeft):
|
||||
estimatedTotalPrintTime = self._estimateTotalPrintTime(progress, printTime)
|
||||
def _setProgressData(self, progress, filepos, printTime, cleanedPrintTime):
|
||||
estimatedTotalPrintTime = self._estimateTotalPrintTime(progress, cleanedPrintTime)
|
||||
|
||||
self._progress = progress
|
||||
self._printTime = printTime
|
||||
self._printTimeLeft = estimatedTotalPrintTime - printTime if (estimatedTotalPrintTime is not None and printTime is not None) else None
|
||||
self._printTimeLeft = estimatedTotalPrintTime - cleanedPrintTime if (estimatedTotalPrintTime is not None and cleanedPrintTime is not None) else None
|
||||
|
||||
self._stateMonitor.setProgress({
|
||||
"completion": self._progress * 100 if self._progress is not None else None,
|
||||
|
|
@ -558,7 +565,7 @@ class Printer():
|
|||
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())
|
||||
self._setProgressData(self._comm.getPrintProgress(), self._comm.getPrintFilepos(), self._comm.getPrintTime(), self._comm.getCleanedPrintTime())
|
||||
|
||||
def mcZChange(self, newZ):
|
||||
"""
|
||||
|
|
@ -833,11 +840,13 @@ class StateMonitor(object):
|
|||
class TimeEstimationHelper(object):
|
||||
|
||||
STABLE_THRESHOLD = 0.1
|
||||
STABLE_COUNTDOWN = 100
|
||||
STABLE_COUNTDOWN = 250
|
||||
STABLE_ROLLING_WINDOW = 250
|
||||
|
||||
def __init__(self):
|
||||
import collections
|
||||
self._distances = collections.deque([], self.__class__.STABLE_ROLLING_WINDOW)
|
||||
self._sum_total = 0
|
||||
self._sum_distance = 0
|
||||
self._count = 0
|
||||
self._stable_counter = None
|
||||
|
||||
|
|
@ -851,7 +860,7 @@ class TimeEstimationHelper(object):
|
|||
self._count += 1
|
||||
|
||||
if old_average_total:
|
||||
self._sum_distance += abs(self.average_total - old_average_total)
|
||||
self._distances.append(abs(self.average_total - old_average_total))
|
||||
|
||||
if -1.0 * self.__class__.STABLE_THRESHOLD < self.average_distance < self.__class__.STABLE_THRESHOLD:
|
||||
if self._stable_counter is None:
|
||||
|
|
@ -870,8 +879,8 @@ class TimeEstimationHelper(object):
|
|||
|
||||
@property
|
||||
def average_distance(self):
|
||||
if not self._count or self._count < 2:
|
||||
if not self._count or self._count < self.__class__.STABLE_ROLLING_WINDOW + 1:
|
||||
return None
|
||||
else:
|
||||
return self._sum_distance / (self._count - 1)
|
||||
return sum(self._distances) / len(self._distances)
|
||||
|
||||
|
|
|
|||
|
|
@ -143,8 +143,10 @@ class MachineCom(object):
|
|||
self._bedTempOffset = 0
|
||||
self._commandQueue = queue.Queue()
|
||||
self._currentZ = None
|
||||
self._heatupWaitStartTime = 0
|
||||
self._heatupWaitStartTime = None
|
||||
self._heatupWaitTimeLost = 0.0
|
||||
self._pauseWaitStartTime = None
|
||||
self._pauseWaitTimeLost = 0.0
|
||||
self._currentExtruder = 0
|
||||
|
||||
self._alwaysSendChecksum = settings().getBoolean(["feature", "alwaysSendChecksum"])
|
||||
|
|
@ -314,20 +316,17 @@ class MachineCom(object):
|
|||
if self._currentFile is None or self._currentFile.getStartTime() is None:
|
||||
return None
|
||||
else:
|
||||
return time.time() - self._currentFile.getStartTime()
|
||||
return time.time() - self._currentFile.getStartTime() - self._pauseWaitTimeLost
|
||||
|
||||
def getPrintTimeRemainingEstimate(self):
|
||||
def getCleanedPrintTime(self):
|
||||
printTime = self.getPrintTime()
|
||||
if printTime is None:
|
||||
return None
|
||||
|
||||
printTime /= 60
|
||||
progress = self._currentFile.getProgress()
|
||||
if progress:
|
||||
printTimeTotal = printTime / progress
|
||||
return printTimeTotal - printTime
|
||||
else:
|
||||
return None
|
||||
cleanedPrintTime = printTime - self._heatupWaitTimeLost
|
||||
if cleanedPrintTime < 0:
|
||||
cleanedPrintTime = 0.0
|
||||
return cleanedPrintTime
|
||||
|
||||
def getTemp(self):
|
||||
return self._temp
|
||||
|
|
@ -388,6 +387,11 @@ class MachineCom(object):
|
|||
if self._currentFile is None:
|
||||
raise ValueError("No file selected for printing")
|
||||
|
||||
self._heatupWaitStartTime = 0
|
||||
self._heatupWaitTimeLost = 0.0
|
||||
self._pauseWaitStartTime = 0
|
||||
self._pauseWaitTimeLost = 0.0
|
||||
|
||||
try:
|
||||
self._currentFile.start()
|
||||
|
||||
|
|
@ -470,6 +474,10 @@ class MachineCom(object):
|
|||
return
|
||||
|
||||
if not pause and self.isPaused():
|
||||
if self._pauseWaitStartTime:
|
||||
self._pauseWaitTimeLost = self._pauseWaitTimeLost + (time.time() - self._pauseWaitStartTime)
|
||||
self._pauseWaitStartTime = None
|
||||
|
||||
self._changeState(self.STATE_PRINTING)
|
||||
if self.isSdFileSelected():
|
||||
self.sendCommand("M24")
|
||||
|
|
@ -482,6 +490,9 @@ class MachineCom(object):
|
|||
"origin": self._currentFile.getFileLocation()
|
||||
})
|
||||
elif pause and self.isPrinting():
|
||||
if not self._pauseWaitStartTime:
|
||||
self._pauseWaitStartTime = time.time()
|
||||
|
||||
self._changeState(self.STATE_PAUSED)
|
||||
if self.isSdFileSelected():
|
||||
self.sendCommand("M25") # pause print
|
||||
|
|
@ -688,12 +699,9 @@ class MachineCom(object):
|
|||
self._callback.mcTempUpdate(self._temp, self._bedTemp)
|
||||
|
||||
#If we are waiting for an M109 or M190 then measure the time we lost during heatup, so we can remove that time from our printing time estimate.
|
||||
if not 'ok' in line:
|
||||
heatingUp = True
|
||||
if self._heatupWaitStartTime != 0:
|
||||
t = time.time()
|
||||
self._heatupWaitTimeLost = t - self._heatupWaitStartTime
|
||||
self._heatupWaitStartTime = t
|
||||
if 'ok' in line and self._heatupWaitStartTime:
|
||||
self._heatupWaitTimeLost = self._heatupWaitTimeLost + (time.time() - self._heatupWaitStartTime)
|
||||
self._heatupWaitStartTime = None
|
||||
elif supportRepetierTargetTemp and ('TargetExtr' in line or 'TargetBed' in line):
|
||||
matchExtr = self._regex_repetierTempExtr.match(line)
|
||||
matchBed = self._regex_repetierTempBed.match(line)
|
||||
|
|
@ -1242,10 +1250,6 @@ class MachineCom(object):
|
|||
self.cancelPrint()
|
||||
return cmd
|
||||
|
||||
def _gcode_M112(self, cmd): # It's an emergency what todo? Canceling the print should be the minimum
|
||||
self.cancelPrint()
|
||||
return cmd
|
||||
|
||||
|
||||
### MachineCom callback ################################################################################################
|
||||
|
||||
|
|
@ -1386,9 +1390,9 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
|
|||
"""
|
||||
Opens the file for reading and determines the file size. Start time won't be recorded until 100 lines in
|
||||
"""
|
||||
PrintingFileInformation.start(self)
|
||||
self._filehandle = open(self._filename, "r")
|
||||
self._lineCount = None
|
||||
self._startTime = None
|
||||
|
||||
def getNext(self):
|
||||
"""
|
||||
|
|
@ -1415,9 +1419,6 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
|
|||
self._lineCount += 1
|
||||
self._filepos = self._filehandle.tell()
|
||||
|
||||
if self._lineCount >= 100 and self._startTime is None:
|
||||
self._startTime = time.time()
|
||||
|
||||
return processedLine
|
||||
except Exception as (e):
|
||||
if self._filehandle is not None:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ import octoprint.printer
|
|||
class EstimationTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.estimation_helper = octoprint.printer.TimeEstimationHelper()
|
||||
self.estimation_helper = type(octoprint.printer.TimeEstimationHelper)(octoprint.printer.TimeEstimationHelper.__name__, (octoprint.printer.TimeEstimationHelper,), {
|
||||
'STABLE_ROLLING_WINDOW': 3,
|
||||
'STABLE_COUNTDOWN': 1
|
||||
})()
|
||||
|
||||
@data(
|
||||
((1.0, 2.0, 3.0, 4.0, 5.0), 3.0),
|
||||
|
|
@ -30,8 +33,8 @@ class EstimationTestCase(unittest.TestCase):
|
|||
self.assertEquals(self.estimation_helper.average_total, expected)
|
||||
|
||||
@data(
|
||||
((1.0, 2.0, 3.0, 4.0, 5.0), 0.5), # average totals: 1.0, 1.5, 2.0, 2.5, 3.0
|
||||
((1.0, 2.0, 0.0, 1.0, 2.0), 0.3) # average totals: 1.0, 1.5, 1.0, 1.0, 1.2
|
||||
((1.0, 2.0, 3.0, 4.0, 5.0), 0.5), # average totals: 1.0, 1.5, 2.0, 2.5, 3.0 => (0.5 + 0.5 + 0.5) / 3 = 0.5
|
||||
((1.0, 2.0, 0.0, 1.0, 2.0), 0.7 / 3) # average totals: 1.0, 1.5, 1.0, 1.0, 1.2 => (0.5 + 0.0 + 0.2) / 3 = 0.7 / 3
|
||||
)
|
||||
@unpack
|
||||
def test_average_distance(self, estimates, expected):
|
||||
|
|
@ -40,3 +43,16 @@ class EstimationTestCase(unittest.TestCase):
|
|||
|
||||
self.assertEquals(self.estimation_helper.average_distance, expected)
|
||||
|
||||
@data(
|
||||
((1.0, 1.0, 1.0, 1.0), False), # average totals: 1.0, 1.0, 1.0, 1.0 => 3.0 / 3 = 1.0
|
||||
((1.0, 1.0, 1.0, 1.0, 1.0), True), # average totals: 1.0, 1.0, 1.0, 1.0, 1.0 => 0.0 / 3 = 0.0
|
||||
((1.0, 2.0, 3.0, 4.0, 5.0), False), # average totals: 1.0, 1.5, 2.0, 2.5, 3.0 => 1.5 / 3 = 0.5
|
||||
((0.0, 0.09, 0.18, 0.27, 0.36), True) # average totals: 0.0, 0.045, 0.09, 0.135, 0.18 => (0.045 + 0.045 + 0.045) / 3 = 0.045
|
||||
)
|
||||
@unpack
|
||||
def test_is_stable(self, estimates, expected):
|
||||
for estimate in estimates:
|
||||
self.estimation_helper.update(estimate)
|
||||
|
||||
self.assertEquals(self.estimation_helper.is_stable(), expected)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue