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:
Gina Häußge 2014-12-16 13:27:31 +01:00
parent 12a3e659b1
commit 5ac908afd4
3 changed files with 62 additions and 36 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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)