forked from M-Labs/kirdy
gui: Add pid autotune to ctrl panel
This commit is contained in:
parent
bfd8c0e43a
commit
dacf9f0fa5
|
@ -29,6 +29,7 @@ from ui.ui_kirdy_qt import Ui_MainWindow
|
|||
from dateutil import tz
|
||||
import math
|
||||
import socket
|
||||
from pid_autotune import PIDAutotune, PIDAutotuneState
|
||||
|
||||
COMMON_ERROR_MSG = "Connection Timeout. Disconnecting."
|
||||
|
||||
|
@ -396,7 +397,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||
{'name': 'Minimum', 'type': 'float', 'step': 100, 'limits': (-1, 1), 'decimals': 6, 'suffix': 'A', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_output_min'},
|
||||
{'name': 'Maximum', 'type': 'float', 'step': 100, 'limits': (-1, 1), 'decimals': 6, 'suffix': 'A', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_output_max'},
|
||||
]},
|
||||
# TODO PID AutoTune
|
||||
{'name': 'PID Auto Tune', 'expanded': False, 'type': 'group', 'children': [
|
||||
{'name': 'Target Temperature', 'type': 'float', 'value': 20, 'step': 0.1, 'format': '{value:.4f} °C'},
|
||||
{'name': 'Test Current', 'type': 'float', 'value': 1000, 'decimals': 6, 'step': 100, 'limits': (-3000, 3000), 'suffix': 'mA'},
|
||||
{'name': 'Temperature Swing', 'type': 'float', 'value': 0.0, 'step': 0.1, 'prefix': '±', 'format': '{value:.4f} °C'},
|
||||
{'name': 'Lookback', 'type': 'float', 'value': 5.0, 'step': 0.1, 'format': '{value:.4f} s'},
|
||||
{'name': 'Run', 'type': 'action', 'tip': 'Run'},
|
||||
]},
|
||||
]},
|
||||
]
|
||||
def __init__(self, args):
|
||||
|
@ -404,6 +411,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||
self.kirdy = Kirdy()
|
||||
self.setupUi(self)
|
||||
|
||||
self.info_box = QtWidgets.QMessageBox()
|
||||
self.info_box.setIcon(QtWidgets.QMessageBox.Icon.Information)
|
||||
|
||||
# Load Global QT Style Sheet Settings
|
||||
qss=os.path.join(os.path.dirname(__file__), "ui/mainwindow.qss")
|
||||
with open(qss,"r") as fh:
|
||||
|
@ -420,6 +430,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||
|
||||
self.max_samples = self.DEFAULT_MAX_SAMPLES
|
||||
|
||||
self.autotuner = PIDAutotune(25)
|
||||
|
||||
self.setup_menu_bar()
|
||||
self._set_up_ctrl_btns()
|
||||
self._set_up_plot_menu()
|
||||
|
@ -627,6 +639,71 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||
self.params[3].sigTreeStateChanged.connect(self.send_command)
|
||||
self.params[3].setValue = _setValue
|
||||
|
||||
@asyncSlot()
|
||||
async def autotune(param):
|
||||
print("button clicked")
|
||||
match self.autotuner.state():
|
||||
case PIDAutotuneState.STATE_OFF:
|
||||
settings = await self.kirdy.device.get_settings_summary()
|
||||
self.autotuner.setParam(
|
||||
param.parent().child('Target Temperature').value(),
|
||||
param.parent().child('Test Current').value() / 1000,
|
||||
param.parent().child('Temperature Swing').value(),
|
||||
1.0 / settings['thermostat']['temp_adc_settings']['rate'],
|
||||
param.parent().child('Lookback').value())
|
||||
self.autotuner.setReady()
|
||||
param.setOpts(title="Stop")
|
||||
self.kirdy_data_watcher.set_report_mode(True)
|
||||
await self.kirdy.thermostat.set_constant_current_control_mode()
|
||||
self.kirdy_data_watcher.report_update_sig.connect(self.autotune_tick)
|
||||
self.loading_spinner.show()
|
||||
self.loading_spinner.start()
|
||||
self.background_task_lbl.setText("Autotuning")
|
||||
case PIDAutotuneState.STATE_READY | PIDAutotuneState.STATE_RELAY_STEP_UP | PIDAutotuneState.STATE_RELAY_STEP_DOWN:
|
||||
self.autotuner.setOff()
|
||||
param.setOpts(title="Run")
|
||||
await self.kirdy.thermostat.set_tec_i_out(0.0)
|
||||
self.kirdy_data_watcher.report_update_sig.disconnect(self.autotune_tick)
|
||||
self.background_task_lbl.setText("Ready.")
|
||||
self.loading_spinner.stop()
|
||||
self.loading_spinner.hide()
|
||||
self.params[3].child('PID Config', 'PID Auto Tune', 'Run').sigActivated.connect(autotune)
|
||||
|
||||
@asyncSlot(dict)
|
||||
async def autotune_tick(self, report):
|
||||
match self.autotuner.state():
|
||||
case PIDAutotuneState.STATE_READY | PIDAutotuneState.STATE_RELAY_STEP_UP | PIDAutotuneState.STATE_RELAY_STEP_DOWN:
|
||||
self.autotuner.run(report['thermostat']['temperature'], report['ts']/1000)
|
||||
await self.kirdy.thermostat.set_tec_i_out(self.autotuner.output())
|
||||
case PIDAutotuneState.STATE_SUCCEEDED:
|
||||
kp, ki, kd = self.autotuner.get_tec_pid()
|
||||
self.autotuner.setOff()
|
||||
self.params[3].child('PID Config', 'PID Auto Tune', 'Run').setOpts(title="Run")
|
||||
await self.kirdy.thermostat.set_pid_kp(kp)
|
||||
await self.kirdy.thermostat.set_pid_ki(ki)
|
||||
await self.kirdy.thermostat.set_pid_kd(kd)
|
||||
await self.kirdy.thermostat.set_pid_control_mode()
|
||||
await self.kirdy.thermostat.set_temperature_setpoint(self.params[3].child('PID Config', 'PID Auto Tune', 'Target Temperature').value())
|
||||
self.kirdy_data_watcher.report_update_sig.disconnect(self.autotune_tick)
|
||||
self.background_task_lbl.setText("Ready.")
|
||||
self.loading_spinner.stop()
|
||||
self.loading_spinner.hide()
|
||||
self.info_box.setWindowTitle("PID AutoTune Success")
|
||||
self.info_box.setText("PID Config has been loaded to Thermostat.\nRegulating temperature.")
|
||||
self.info_box.show()
|
||||
|
||||
case PIDAutotuneState.STATE_FAILED:
|
||||
self.autotuner.setOff()
|
||||
self.params[3].child('PID Config', 'PID Auto Tune', 'Run').setOpts(title="Run")
|
||||
await self.kirdy.thermostat.set_tec_i_out(0.0)
|
||||
self.kirdy_data_watcher.report_update_sig.disconnect(self.autotune_tick)
|
||||
self.background_task_lbl.setText("Ready.")
|
||||
self.loading_spinner.stop()
|
||||
self.loading_spinner.hide()
|
||||
self.info_box.setWindowTitle("PID Autotune Failed")
|
||||
self.info_box.setText("PID Autotune is failed.")
|
||||
self.info_box.show()
|
||||
|
||||
async def _on_connection_changed(self, result, hard_reset=False):
|
||||
def ctrl_panel_setEnable(result):
|
||||
self.ld_status.setEnabled(result)
|
||||
|
|
|
@ -21,6 +21,7 @@ class PIDAutotuneState(Enum):
|
|||
STATE_RELAY_STEP_DOWN = 'relay step down'
|
||||
STATE_SUCCEEDED = 'succeeded'
|
||||
STATE_FAILED = 'failed'
|
||||
STATE_READY = 'ready'
|
||||
|
||||
|
||||
class PIDAutotune:
|
||||
|
@ -60,6 +61,21 @@ class PIDAutotune:
|
|||
self._Ku = 0
|
||||
self._Pu = 0
|
||||
|
||||
def setParam(self, target, step, noiseband, sampletime, lookback):
|
||||
self._setpoint = target
|
||||
self._outputstep = step
|
||||
self._out_max = step
|
||||
self._out_min = -step
|
||||
self._noiseband = noiseband
|
||||
self._inputs = deque(maxlen=round(lookback / sampletime))
|
||||
|
||||
def setReady(self):
|
||||
self._state = PIDAutotuneState.STATE_READY
|
||||
self._peak_count = 0
|
||||
|
||||
def setOff(self):
|
||||
self._state = PIDAutotuneState.STATE_OFF
|
||||
|
||||
def state(self):
|
||||
"""Get the current state."""
|
||||
return self._state
|
||||
|
@ -106,7 +122,8 @@ class PIDAutotune:
|
|||
|
||||
if (self._state == PIDAutotuneState.STATE_OFF
|
||||
or self._state == PIDAutotuneState.STATE_SUCCEEDED
|
||||
or self._state == PIDAutotuneState.STATE_FAILED):
|
||||
or self._state == PIDAutotuneState.STATE_FAILED
|
||||
or self._state == PIDAutotuneState.STATE_READY):
|
||||
self._state = PIDAutotuneState.STATE_RELAY_STEP_UP
|
||||
|
||||
self._last_run_timestamp = now
|
||||
|
|
Loading…
Reference in New Issue