Skip to content

Commit

Permalink
-improve btbreak operation
Browse files Browse the repository at this point in the history
-improve autoDROP accuracy for most setups
-add resolution estimate to profileQuality()
-
  • Loading branch information
roasterdave committed Nov 13, 2023
1 parent 3b06527 commit e3bb21c
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 63 deletions.
23 changes: 18 additions & 5 deletions src/artisanlib/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
from artisanlib.filters import LiveMedian
from artisanlib.dialogs import ArtisanMessageBox
from artisanlib.types import SerialSettings
from artisanlib.types import BTBreakParams

# import artisan.plus module
from plus.util import roastLink
Expand Down Expand Up @@ -306,7 +307,7 @@ class tgraphcanvas(FigureCanvas):
'segmentpickflag', 'segmentdeltathreshold', 'segmentsamplesthreshold', 'stats_summary_rect', 'title_text', 'title_artist', 'title_width',
'background_title_width', 'xlabel_text', 'xlabel_artist', 'xlabel_width', 'lazyredraw_on_resize_timer', 'mathdictionary_base',
'ambient_pressure_sampled', 'ambient_humidity_sampled', 'ambientTemp_sampled', 'backgroundmovespeed', 'chargeTimerPeriod', 'flavors_default_value',
'fmt_data_ON', 'l_subtitle', 'projectDeltaFlag', 'weight_units']
'fmt_data_ON', 'l_subtitle', 'projectDeltaFlag', 'weight_units', 'btbreak_params']


def __init__(self, parent:QWidget, dpi:int, locale:str, aw:'ApplicationWindow') -> None:
Expand Down Expand Up @@ -476,6 +477,20 @@ def __init__(self, parent:QWidget, dpi:int, locale:str, aw:'ApplicationWindow')
self.decay_weights:Optional[List[int]] = None
self.temp_decay_weights:Optional[List[int]] = None

# used by BTbreak
self.btbreak_params:BTBreakParams = {
'd_drop': -0.34,
'd_charge': -0.67,
'tight': 3,
'loose': 5,
'f': 2.5,
'maxdpre': 6.4,
'f_dtwice': 1.5,
'dpre_dpost_diff': 0.78,
'offset_charge': 0.5,
'offset_drop': 0.2
}

self.flavorlabels = list(self.artisanflavordefaultlabels)
#Initial flavor parameters.
self.flavors_default_value:float = 5.
Expand Down Expand Up @@ -3902,8 +3917,7 @@ def sample_processing(self, local_flagstart:bool, temp1_readings:List[float], te
# only if BT > 77C/170F
if self.autoChargeIdx == 0 and self.autoChargeFlag and self.autoCHARGEenabled and self.timeindex[0] < 0 and length_of_qmc_timex >= 5 and \
((self.mode == 'C' and sample_temp2[-1] > 77) or (self.mode == 'F' and sample_temp2[-1] > 170)):
o = 0.5 if self.mode == 'C' else 0.5 * 1.8
b = self.aw.BTbreak(length_of_qmc_timex - 1,o) # call BTbreak with last index
b = self.aw.BTbreak(length_of_qmc_timex - 1,event='CHARGE') # call BTbreak with last index
if b > 0:
# we found a BT break at the current index minus b
self.autoChargeIdx = length_of_qmc_timex - b
Expand Down Expand Up @@ -3932,8 +3946,7 @@ def sample_processing(self, local_flagstart:bool, temp1_readings:List[float], te
if self.autoDropIdx == 0 and self.autoDropFlag and self.autoDROPenabled and self.timeindex[0] > -1 and not self.timeindex[6] and \
length_of_qmc_timex >= 5 and ((self.mode == 'C' and sample_temp2[-1] > 160) or (self.mode == 'F' and sample_temp2[-1] > 320)) and\
((sample_timex[-1] - sample_timex[self.timeindex[0]]) > 420):
o = 0.2 if self.mode == 'C' else 0.2 * 1.8
b = self.aw.BTbreak(length_of_qmc_timex - 1,o)
b = self.aw.BTbreak(length_of_qmc_timex - 1,event='DROP') # call BTbreak with last index
if b > 0:
# we found a BT break at the current index minus b
self.autoDropIdx = length_of_qmc_timex - b
Expand Down
86 changes: 45 additions & 41 deletions src/artisanlib/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14775,6 +14775,17 @@ def ndec(num):
ndec_arr = numpy.array([ndec(x) for x in bt])
avgDecimal = numpy.average(ndec_arr)
maxDecimal = numpy.amax(ndec_arr)

# Calculate the resolution from the BT values
# Sort the numbers in ascending order #dave
# Calculate the differences between successive numbers #dave
# Find the smallest non-zero difference #dave
# Exception if there are no non-zero differences
try:
resolution = numpy.min(numpy.diff(numpy.sort(bt))[numpy.nonzero(numpy.diff(numpy.sort(bt)))])
except Exception: # pylint: disable=broad-except #dave
resolution = float('nan') #dave

str_modeChanged = ''
if profileMode in {'C', 'F'} and self.qmc.mode != profileMode:
str_modeChanged = '*Result not reliable, the temperature mode was changed'
Expand Down Expand Up @@ -14828,6 +14839,7 @@ def ndec(num):
f'Profile quality metrics'
f'\n Title: {self.qmc.title}'
f'\n Meter: {meter}'
f'\n Resolution: {resolution:.2E} {str_modeChanged}'
f'\n Average decimals: {avgDecimal:.2f} {str_modeChanged}'
f'\n Max decimals: {maxDecimal:.2f} {str_modeChanged}'
f'\n Total Samples: {totalSamples}'
Expand Down Expand Up @@ -21600,64 +21612,56 @@ def findDryEnd(self,TP_index=None,phasesindex=1):
index = i
return index

def checkTop(self, offset, p0, p1, p2, p3, p4, p5, twice=False):
# _log.info('PRINT checkTop(%s,%s,%s,%s,%s,%s,%s,%s)',offset,p0,p1,p2,p3,p4,p5,twice)
def checkTop(self, d, offset, p0, p1, p2, p3, p4, p5, twice=False):
d1 = p0 - p1
d2 = p1 - p2
#--
d3 = p4 - p3
d4 = p5 - p4
dpre = (d1 + d2) / 2.0
dpost = (d3 + d4) / 2.0
if self.qmc.mode == 'C':
f = 2.5
d = -0.67 #-0.55 # minimum temperature delta of the two legs after the event to prevent too early recognition based on noise
maxdpre = 6.4 # limit the difference between pre and post to catch the case where temp before the event rises strong
else:
f = 2.8 * 1.8
d = -1.2 #-0,99 # minimum temperature delta of the two legs after the event to prevent too early recognition based on noise
maxdpre = 11.52

f = self.qmc.btbreak_params['f']
maxdpre = self.qmc.btbreak_params['maxdpre']
f_dtwice = self.qmc.btbreak_params['f_dtwice']
dpre_dpost_diff = self.qmc.btbreak_params['dpre_dpost_diff']

#scale parameters for temperature units
if self.qmc.mode == 'F':
f *= 1.8
d *= 1.8
maxdpre *= 1.8
dpre_dpost_diff *= 1.8

if twice:
d = d*1.5
# _log.info('PRINT checkTop => %s, %s, %s, %s => %s | %s | %s', d3, d4, abs(dpost), (offset + (f * abs(dpre))), dpre, dpost, -dpre - dpost)
# return bool(d3 < .0 and d4 < .0 and (abs(dpost) > (offset + (f * abs(dpre))))) # v2.8
d = d * f_dtwice

# improved variant requesting for a certain minimum delta between the reading of interest and the next two post event legs:
return bool(d3 < d and d4 < d and ((abs(dpost) > min(maxdpre, offset + (f * abs(dpre)))) or (dpost < 0 and dpre < 0 and (-dpre - dpost) > 1.4)))
return bool(d3 < d and d4 < d and ((abs(dpost) > min(maxdpre, offset + (f * abs(dpre))))
or (dpost < 0 and dpre < 0 and (-dpre - dpost) > dpre_dpost_diff)))

# returns True if a BT break at i-2 is detected
# i the index of the last reading to be considered to proof that i-2 (or i-4) is the index of the BT break
# idea:
# . average delta before i-2 is not negative
# . average delta after i-2 is negative and twice as high (absolute) as the one before
def BTbreak(self,i,offset):
# _log.info('PRINT BTbreak(%s,%s) => %s',i,offset,self.qmc.temp2[i])
if len(self.qmc.timex)>5 and i < len(self.qmc.timex):
if self.checkTop(offset,self.qmc.temp2[i-5],self.qmc.temp2[i-4],self.qmc.temp2[i-3],self.qmc.temp2[i-2],self.qmc.temp2[i-1],self.qmc.temp2[i]):
# _log.info('PRINT BTbreak tight success')
return 3
if len(self.qmc.timex)>10 and self.checkTop(offset,self.qmc.temp2[i-10],self.qmc.temp2[i-8],self.qmc.temp2[i-6],self.qmc.temp2[i-4],self.qmc.temp2[i-2],self.qmc.temp2[i],twice=True):
return 5
# _log.info('PRINT BTbreak loose success')
# d is minimum temperature delta of the two legs after the event to prevent too early recognition based on noise
def BTbreak(self,i,event):
if event in ['DROP','drop']:
offset = self.qmc.btbreak_params['offset_drop']
d = self.qmc.btbreak_params['d_drop']
else: #CHARGE
offset = self.qmc.btbreak_params['offset_charge']
d = self.qmc.btbreak_params['d_charge']

#dave -- must revisit the i>5 term!!!
if len(self.qmc.timex)>5 and i>4 and i < len(self.qmc.timex): #'i>4' prevents reading temp2[-1] or worse when using BTbreak post recording
if self.checkTop(d,offset,self.qmc.temp2[i-5],self.qmc.temp2[i-4],self.qmc.temp2[i-3],self.qmc.temp2[i-2],self.qmc.temp2[i-1],self.qmc.temp2[i]):
return self.qmc.btbreak_params['tight']
if len(self.qmc.timex)>10 and i>10 and self.checkTop(d,offset,self.qmc.temp2[i-10],self.qmc.temp2[i-8],self.qmc.temp2[i-6],self.qmc.temp2[i-4],self.qmc.temp2[i-2],self.qmc.temp2[i],twice=True):
return self.qmc.btbreak_params['loose']
return 0

# this can be used to find the CHARGE index as well as the DROP index by using
# 0 or the DRY index as start index, respectively
def findBTbreak(self,start_index=0,end_index=0,offset=0.5):
result = 0
# determine average deltaBT wrt. the two previous measurements
# the deltaBT values wrt. the next two measurements must by twice as high and negative
# then our current measurement is the one of CHARGE/DROP
for i in range(start_index,len(self.qmc.timex)):
if end_index and i > end_index:
break
if i>3:
o = offset if self.qmc.mode == 'C' else offset * 1.8
b = self.BTbreak(i,o)
if b > 0:
result = i + 1 - b
break
return result

# updates AUC guide (expected time to hit target AUC; self.qmc.AUCguideTime) based on current AUC, target, base, and RoR
def updateAUCguide(self):
if (len(self.qmc.delta2) > 0 and self.qmc.delta2[-1] is not None and self.qmc.delta2[-1] > 0 and # we have a positive BT RoR
Expand Down
17 changes: 0 additions & 17 deletions src/artisanlib/roast_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,22 +623,6 @@ def __init__(self, parent:QWidget, aw:'ApplicationWindow', activeTab:int = 0) ->
self.chargeedit.setMaximumWidth(50)
self.chargeedit.setMinimumWidth(50)
chargelabel.setBuddy(self.chargeedit)
self.charge_idx = 0
self.drop_idx = 0
#charge_str = ""
drop_str = ''
if len(self.aw.qmc.timex):
TP_index = self.aw.findTP()
if self.aw.qmc.timeindex[1]:
#manual dryend available
dryEndIndex = self.aw.qmc.timeindex[1]
else:
#find when dry phase ends
dryEndIndex = self.aw.findDryEnd(TP_index)
self.charge_idx = self.aw.findBTbreak(0,dryEndIndex,offset=0.5)
self.drop_idx = self.aw.findBTbreak(dryEndIndex,offset=0.2)
if self.drop_idx not in (0, self.aw.qmc.timeindex[6]):
drop_str = stringfromseconds(self.aw.qmc.timex[self.drop_idx]-self.aw.qmc.timex[self.aw.qmc.timeindex[0]])
drylabel = QLabel('<b>' + QApplication.translate('Label', 'DRY END') + '</b>')
drylabel.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
drylabel.setStyleSheet("background-color:'orange';")
Expand Down Expand Up @@ -729,7 +713,6 @@ def __init__(self, parent:QWidget, aw:'ApplicationWindow', activeTab:int = 0) ->
self.dropedit.setMaximumWidth(50)
self.dropedit.setMinimumWidth(50)
droplabel.setBuddy(self.dropedit)
self.dropestimate = QLabel(drop_str)
coollabel = QLabel('<b>' + QApplication.translate('Label', 'COOL') + '</b>')
coollabel.setStyleSheet("background-color:'#6666ff';")
coollabel.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
Expand Down
12 changes: 12 additions & 0 deletions src/artisanlib/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,3 +486,15 @@ class SerialSettings(TypedDict):
stopbits: int
parity: str
timeout: float

class BTBreakParams(TypedDict, total=False):
offset_charge: float
offset_drop: float
d_drop: float
d_charge: float
tight: int
loose: int
f: float
maxdpre: float
f_dtwice: float
dpre_dpost_diff: float

0 comments on commit e3bb21c

Please sign in to comment.