Skip to content

Commit

Permalink
adds option to start the RampSoak time on PID ON
Browse files Browse the repository at this point in the history
  • Loading branch information
MAKOMO committed Oct 20, 2024
1 parent 8bd8b86 commit f29aba5
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 16 deletions.
8 changes: 3 additions & 5 deletions src/artisanlib/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ def __init__(self, parent:QWidget, dpi:int, locale:str, aw:'ApplicationWindow')
# default delay between readings in milliseconds
self.default_delay: Final[int] = 2000 # default 2s
self.delay:int = self.default_delay
self.min_delay: Final[int] = 250 #500 # 1000 # Note that a 0.25s min delay puts a lot of performance pressure on the app
self.min_delay: Final[int] = 100 #250 # 1000 # Note that already a 0.25s min delay puts a lot of performance pressure on the app

# extra event sampling interval in milliseconds. If 0, then extra sampling commands are sent "in sync" with the standard sampling commands
self.extra_event_sampling_delay:int = 0 # sync, 0.5s, 1.0s, 1.5s,.., 5s => 0, 500, 1000, 1500, .. # 0, 500, 1000, 1500, ...
Expand Down Expand Up @@ -15853,11 +15853,9 @@ def backgroundXTat(self, n:int, seconds:float, relative:bool = False, smoothed:b
if n % 2 == 0:
# even
tempBX = self.stemp1BX if smoothed else self.temp1BX
# odd
elif smoothed:
tempBX = self.stemp2BX
else:
tempBX = self.temp2BX
# odd
tempBX = self.stemp2BX if smoothed else self.temp2BX
c = n // 2
if len(tempBX)>c:
temp = tempBX[c]
Expand Down
2 changes: 2 additions & 0 deletions src/artisanlib/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17304,6 +17304,7 @@ def settingsLoad(self, filename:Optional[str] = None, theme:bool = False, machin
#restore TC4/Arduino PID settings
settings.beginGroup('ArduinoPID')
self.pidcontrol.pidOnCHARGE = bool(toBool(settings.value('pidOnCHARGE',self.pidcontrol.pidOnCHARGE)))
self.pidcontrol.RStimeAfterCHARGE = bool(toBool(settings.value('RStimeAfterCHARGE',self.pidcontrol.RStimeAfterCHARGE)))
self.pidcontrol.loadpidfrombackground = bool(toBool(settings.value('loadpidfrombackground',self.pidcontrol.loadpidfrombackground)))
self.pidcontrol.createEvents = bool(toBool(settings.value('createEvents',self.pidcontrol.createEvents)))
self.pidcontrol.loadRampSoakFromProfile = bool(toBool(settings.value('loadRampSoakFromProfile',self.pidcontrol.loadRampSoakFromProfile)))
Expand Down Expand Up @@ -18963,6 +18964,7 @@ def saveAllSettings(self, settings:QSettings, default_settings:Optional[Dict[str
#save pid settings (only key and value[0])
settings.beginGroup('ArduinoPID')
self.settingsSetValue(settings, default_settings, 'pidOnCHARGE',self.pidcontrol.pidOnCHARGE, read_defaults)
self.settingsSetValue(settings, default_settings, 'RStimeAfterCHARGE',self.pidcontrol.RStimeAfterCHARGE, read_defaults)
self.settingsSetValue(settings, default_settings, 'loadpidfrombackground',self.pidcontrol.loadpidfrombackground, read_defaults)
self.settingsSetValue(settings, default_settings, 'createEvents',self.pidcontrol.createEvents, read_defaults)
self.settingsSetValue(settings, default_settings, 'loadRampSoakFromProfile',self.pidcontrol.loadRampSoakFromProfile, read_defaults)
Expand Down
41 changes: 35 additions & 6 deletions src/artisanlib/pid_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ def setpidPXR(self, var:str, v:float) -> None:
self.aw.qmc.adderror(message)

def calcSV(self, tx:float) -> Optional[float]:
# tx is the timestamp recorded, NOT the time displayed to the user after CHARGE
if self.aw.qmc.background:
# Follow Background mode
if self.aw.qmc.swapETBT: # we observe the BT
Expand Down Expand Up @@ -1134,11 +1135,11 @@ def fujiCrc16(string:bytes) -> int:
###################################################################################

class PIDcontrol:
__slots__ = [ 'aw', 'pidActive', 'sv', 'pidOnCHARGE', 'loadpidfrombackground', 'createEvents', 'loadRampSoakFromProfile', 'loadRampSoakFromBackground', 'svLen', 'svLabel',
__slots__ = [ 'aw', 'pidActive', 'sv', 'pidOnCHARGE', 'RStimeAfterCHARGE', 'loadpidfrombackground', 'createEvents', 'loadRampSoakFromProfile', 'loadRampSoakFromBackground', 'svLen', 'svLabel',
'svValues', 'svRamps', 'svSoaks', 'svActions', 'svBeeps', 'svDescriptions','svTriggeredAlarms', 'RSLen', 'RS_svLabels', 'RS_svValues', 'RS_svRamps', 'RS_svSoaks',
'RS_svActions', 'RS_svBeeps', 'RS_svDescriptions', 'svSlider', 'svButtons', 'svMode', 'svLookahead', 'dutySteps', 'svSliderMin', 'svSliderMax', 'svValue',
'dutyMin', 'dutyMax', 'pidKp', 'pidKi', 'pidKd', 'pOnE', 'pidSource', 'pidCycle', 'pidPositiveTarget', 'pidNegativeTarget', 'invertControl',
'sv_smoothing_factor', 'sv_decay_weights', 'previous_svs', 'time_pidON', 'current_ramp_segment', 'current_soak_segment', 'ramp_soak_engaged',
'sv_smoothing_factor', 'sv_decay_weights', 'previous_svs', 'time_pidON', 'source_reading_pidON', 'current_ramp_segment', 'current_soak_segment', 'ramp_soak_engaged',
'RS_total_time', 'slider_force_move', 'positiveTargetRangeLimit', 'positiveTargetMin', 'positiveTargetMax', 'negativeTargetRangeLimit',
'negativeTargetMin', 'negativeTargetMax', 'derivative_filter']

Expand All @@ -1148,6 +1149,7 @@ def __init__(self, aw:'ApplicationWindow') -> None:
self.sv:Optional[float] = None # the last sv send to the Arduino
#
self.pidOnCHARGE:bool = False
self.RStimeAfterCHARGE = True # if True RS time is taken from CHARGE if FALSE it is the time after the PID was last started
self.loadpidfrombackground = False # if True, p-i-d parameters pidKp, pidKi, pidKd, pidSource, pOnE and svLookahead are set from the background profile
self.createEvents:bool = False
self.loadRampSoakFromProfile:bool = False
Expand Down Expand Up @@ -1211,6 +1213,7 @@ def __init__(self, aw:'ApplicationWindow') -> None:
self.previous_svs:List[float] = []
# time @ PID ON
self.time_pidON:float = 0 # in monitoring mode, ramp-soak times are interpreted w.r.t. the time after the PID was turned on and not the time after CHARGE as during recording
self.source_reading_pidON:float = 0 # the reading of the selected source on PID ON (to be used as start point for the first RAMP/SOAK pattern)
self.current_ramp_segment:int = 0 # the RS segment currently active. Note that this is 1 based, 0 indicates that no segment has started yet
self.current_soak_segment:int = 0 # the RS segment currently active. Note that this is 1 based, 0 indicates that no segment has started yet
self.ramp_soak_engaged:int = 1 # set to 0, disengaged, after the RS pattern was processed fully
Expand Down Expand Up @@ -1343,14 +1346,33 @@ def pidModeInit(self) -> None:
self.RS_total_time = self.RStotalTime(self.svRamps,self.svSoaks)
self.svTriggeredAlarms = [False]*self.svLen

if self.aw.qmc.flagstart or len(self.aw.qmc.on_timex)<1:
if self.aw.qmc.flagstart:
self.time_pidON = self.aw.qmc.timex[-1]
elif len(self.aw.qmc.on_timex)<1:
self.time_pidON = 0
else:
self.time_pidON = self.aw.qmc.on_timex[-1]
if self.svMode == 1:
# turn the timer LCD color blue if in RS mode and not recording
self.aw.setTimerColor('rstimer')

# remember current pidSource reading
self.source_reading_pidON = 0
if self.pidSource == 1: # we observe the BT
self.source_reading_pidON = self.aw.qmc.temp2[-1]
elif self.pidSource == 2: # we observe the ET
self.source_reading_pidON = self.aw.qmc.temp1[-1]
elif self.pidSource>2: # we observe an extra curve
n = self.pidSource-3
c = n // 2
if n % 2 == 0:
tempX = self.aw.qmc.extratemp1 if self.aw.qmc.flagstart else self.aw.qmc.on_extratemp1
else:
tempX = self.aw.qmc.extratemp2 if self.aw.qmc.flagstart else self.aw.qmc.on_extratemp2
if len(tempX)>c:
self.source_reading_pidON = tempX[c][-1]


# the internal software PID should be configured on ON, but not be activated yet to warm it up
def confSoftwarePID(self) -> None:
if self.externalPIDControl() not in [1, 2, 4] and not(self.aw.qmc.device == 19 and self.aw.qmc.PIDbuttonflag) and self.aw.qmc.Controlbuttonflag:
Expand Down Expand Up @@ -1498,7 +1520,7 @@ def svRampSoak(self, t:float) -> Optional[float]:
segment_end_time = 0 # the (end) time of the segments
prev_segment_end_time = 0 # the (end) time of the previous segment
segment_start_sv = 0. # the (target) sv of the segment
prev_segment_start_sv = 0. # the (target) sv of the previous segment
prev_segment_start_sv = self.source_reading_pidON # the (target) sv of the previous segment; initialized to the reading of the pid source on PID ON
for i, v in enumerate(self.svValues):
# Ramp
if self.svRamps[i] != 0:
Expand Down Expand Up @@ -1554,10 +1576,17 @@ def smooth_sv(self, sv:float) -> float:

# returns None if in manual mode or no other sv (via ramp/soak or follow mode) defined
def calcSV(self, tx:float) -> Optional[float]:
# tx is the timestamp recorded, NOT the time displayed to the user after CHARGE
if self.svMode == 1:
# Ramp/Soak mode
# actual time (after CHARGE) on recording and time after PID ON on monitoring:
return self.svRampSoak(tx - self.time_pidON)
# actual time (after CHARGE) on recording (if CHARGE and RStimeAfterCHARGE) and time after PID ON (on monitoring or if RStimeAfterCHARGE):
time = tx
if not self.aw.qmc.flagstart or not self.RStimeAfterCHARGE:
time = time - self.time_pidON
elif self.RStimeAfterCHARGE and self.aw.qmc.timeindex[0] > -1:
# after CHARGE
time = time - self.aw.qmc.timex[self.aw.qmc.timeindex[0]]
return self.svRampSoak(time)
if self.svMode == 2 and self.aw.qmc.background:
# Follow Background mode
if self.aw.qmc.device == 19 and self.externalPIDControl(): # in case we run TC4 with the PIDfirmware
Expand Down
16 changes: 15 additions & 1 deletion src/artisanlib/pid_dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,19 @@ def __init__(self, parent:QWidget, aw:'ApplicationWindow', activeTab:int = 0) ->
flagsLayout.addWidget(self.loadRampSoakFromBackground)
flagsLayout.addStretch()


time_label = QLabel(QApplication.translate('Label', 'Time starts at'))
self.radioTimeAfterCHARGE = QRadioButton(QApplication.translate('Label','CHARGE'))
self.radioTimeAfterPIDON = QRadioButton(QApplication.translate('Button','PID ON'))
if self.aw.pidcontrol.RStimeAfterCHARGE:
self.radioTimeAfterCHARGE.setChecked(True)
else:
self.radioTimeAfterPIDON.setChecked(True)
radioButtonsLayout = QHBoxLayout()
radioButtonsLayout.addStretch()
radioButtonsLayout.addWidget(time_label)
radioButtonsLayout.addWidget(self.radioTimeAfterCHARGE)
radioButtonsLayout.addWidget(self.radioTimeAfterPIDON)
radioButtonsLayout.addStretch()

buttonLayout = QHBoxLayout()
buttonLayout.addStretch()
Expand All @@ -792,6 +804,7 @@ def __init__(self, parent:QWidget, aw:'ApplicationWindow', activeTab:int = 0) ->

tab2Layout.addLayout(buttonLayout)
tab2Layout.addStretch()
tab2Layout.addLayout(radioButtonsLayout)
tab2Layout.addLayout(flagsLayout)


Expand Down Expand Up @@ -1180,6 +1193,7 @@ def close(self) -> bool:
self.aw.pidcontrol.setPID(kp,ki,kd,source,cycle,pOnE)
#
self.aw.pidcontrol.pidOnCHARGE = self.startPIDonCHARGE.isChecked()
self.aw.pidcontrol.RStimeAfterCHARGE = self.radioTimeAfterCHARGE.isChecked()
self.aw.pidcontrol.loadpidfrombackground = self.loadPIDfromBackground.isChecked()
self.aw.pidcontrol.createEvents = self.createEvents.isChecked()
self.aw.pidcontrol.loadRampSoakFromProfile = self.loadRampSoakFromProfile.isChecked()
Expand Down
6 changes: 4 additions & 2 deletions src/artisanlib/santoker.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,11 @@ def register_reading(self, target:bytes, data:bytes) -> None:
if target == self.BOARD:
self._board = value / 10.0
elif target == self.BT:
self._bt = value / 10.0
BT = value / 10.0
self._bt = (BT if self._bt == -1 else (2*BT + self._bt)/3)
elif target == self.ET:
self._et = value / 10.0
ET = value / 10.0
self._et = (ET if self._et == -1 else (2*ET + self._et)/3)
elif target == self.BT_ROR:
self._bt_ror = value / 10.0
elif target == self.ET_ROR:
Expand Down
7 changes: 5 additions & 2 deletions src/artisanlib/santoker_r.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,11 @@ def notify_callback(self, _sender:'BleakGATTCharacteristic', data:bytearray) ->
if self._logging:
_log.debug('notify: %s', data)
if len(data) > 3:
self._BT = int.from_bytes(data[0:2], 'big') / 10
self._ET = int.from_bytes(data[2:4], 'big') / 10
BT = int.from_bytes(data[0:2], 'big') / 10
ET = int.from_bytes(data[2:4], 'big') / 10
# BLE delivers a new reading about every 0.5sec which we average
self._BT = (BT if self._BT == -1 else (2*BT + self._BT)/3)
self._ET = (ET if self._ET == -1 else (2*ET + self._ET)/3)

def getBT(self) -> float:
return self._BT
Expand Down
1 change: 1 addition & 0 deletions wiki/ReleaseHistory.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ v3.0.3
- adds [Loring](https://artisan-scope.org/machines/loring/) 'auto' setup which picks up CHARGE and DROP events set at the machine
- adds control function to [Diedrich DR](https://artisan-scope.org/machines/diedrich/) machine setup and adds [Diedrich CR](https://artisan-scope.org/machines/diedrich/) machine setup
- adds support for [Phidget Stepper Motor Controllers](https://artisan-scope.org/devices/phidgets/#47-stepper-motor-control) ([Discussion #891](../../../discussions/891) and [PR #1715](../../../pull/1715))
- adds option to start the RampSoak timer on PID ON instead on CHARGE ([Discussion #1720](../../../discussions/1720))

* CHANGES
- automatically start of the scheduler on connected to [artisan.plus](https://artisan.plus) if there are incompleted scheduled items
Expand Down

0 comments on commit f29aba5

Please sign in to comment.