diff --git a/pytec/setup.py b/pytec/setup.py index c084cdf..210b175 100644 --- a/pytec/setup.py +++ b/pytec/setup.py @@ -14,5 +14,5 @@ setup( "tec_qt = tec_qt:main", ] }, - py_modules=['tec_qt', 'ui_tec_qt'], + py_modules=['tec_qt', 'ui_tec_qt', 'autotune'], ) diff --git a/pytec/tec_qt.py b/pytec/tec_qt.py index a885501..e3b1da8 100644 --- a/pytec/tec_qt.py +++ b/pytec/tec_qt.py @@ -15,6 +15,7 @@ import asyncio from pytec.aioclient import Client, StoppedConnecting import qasync from qasync import asyncSlot, asyncClose +from autotune import PIDAutotune, PIDAutotuneState # pyuic6 -x tec_qt.ui -o ui_tec_qt.py from ui_tec_qt import Ui_MainWindow @@ -257,7 +258,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): {'name': 'Target Temperature', 'type': 'float', 'value': 20, 'step': 0.1, 'siPrefix': True, 'suffix': '°C'}, {'name': 'Test Current', 'type': 'float', 'value': 1, 'step': 0.1, 'siPrefix': True, 'suffix': 'A'}, {'name': 'Temperature Swing', 'type': 'float', 'value': 1.5, 'step': 0.1, 'siPrefix': True, 'prefix': '±', 'suffix': '°C'}, - {'name': 'Run', 'type': 'action', 'tip': 'Run'}, + {'name': 'Run', 'type': 'action', 'tip': 'Run', 'children': [ + {'name': 'Autotuning...', 'type': 'progress', 'value': 0, 'readonly': True, 'visible': False}, + ]}, ]}, ]}, {'name': 'Save to flash', 'type': 'action', 'tip': 'Save config to thermostat, applies on reset'}, @@ -299,6 +302,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): for ch in range(2) ] + self.autotuners = [ + PIDAutotune(25) + for _ in range(2) + ] + self.hw_rev_data = None if args.connect: @@ -656,6 +664,61 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.params[i].child('Load from flash').sigActivated.connect(load) + @asyncSlot() + async def autotune(param, ch=i): + match self.autotuners[ch].state(): + case PIDAutotuneState.STATE_OFF: + self.autotuners[ch].setParam( + param.parent().child('Target Temperature').value(), + param.parent().child('Test Current').value(), + param.parent().child('Temperature Swing').value(), + self.report_refresh_spin.value(), + 3) + self.autotuners[ch].setReady() + param.child('Autotuning...').show(True) + param.setOpts(title="Stop") + self.client_watcher.report_update.connect(self.autotune_tick) + case PIDAutotuneState.STATE_READY | PIDAutotuneState.STATE_RELAY_STEP_UP | PIDAutotuneState.STATE_RELAY_STEP_DOWN: + self.autotuners[ch].setOff() + param.child('Autotuning...').hide() + param.setOpts(title="Run") + await self.client.set_param('pwm', ch, 'i_set', 0) + self.client_watcher.report_update.disconnect(self.autotune_tick) + param.child('Autotuning...').setValue(0) + + self.params[i].child('PID Config', 'PID Auto Tune', 'Run').sigActivated.connect(autotune) + + @asyncSlot(list) + async def autotune_tick(self, report): + for channel_report in report: + channel = channel_report['channel'] + match self.autotuners[channel].state(): + case PIDAutotuneState.STATE_READY | PIDAutotuneState.STATE_RELAY_STEP_UP | PIDAutotuneState.STATE_RELAY_STEP_DOWN: + self.autotuners[channel].run(channel_report['temperature'], channel_report['time']) + progress_bar = self.params[channel].child('PID Config', 'PID Auto Tune', 'Run', 'Autotuning...') + if progress_bar.value() < 99: + progress_bar.setValue(progress_bar.value() + 1) # TODO: Measure the progress better + await self.client.set_param('pwm', channel, 'i_set', self.autotuners[channel].output()) + case PIDAutotuneState.STATE_SUCCEEDED: + kp, ki, kd = self.autotuners[channel].get_tec_pid() + self.autotuners[channel].setOff() + self.params[channel].child('PID Config', 'PID Auto Tune', 'Run').setOpts(title="Run") + self.params[channel].child('PID Config', 'PID Auto Tune', 'Run', 'Autotuning...').hide() + await self.client.set_param('pid', channel, 'kp', kp) + await self.client.set_param('pid', channel, 'ki', ki) + await self.client.set_param('pid', channel, 'kd', kd) + await self.client.set_param('pwm', channel, 'pid') + await self.client.set_param('pid', channel, 'target', self.params[channel].child("PID Config", "PID Auto Tune", "Target Temperature").value()) + self.client_watcher.report_update.disconnect(self.autotune_tick) + self.params[channel].child('PID Config', 'PID Auto Tune', 'Run', 'Autotuning...').setValue(0) + case PIDAutotuneState.STATE_FAILED: + self.autotuners[channel].setOff() + self.params[channel].child('PID Config', 'PID Auto Tune', 'Run', 'Autotuning...').hide() + self.params[channel].child('PID Config', 'PID Auto Tune', 'Run').setOpts(title="Run") + await self.client.set_param('pwm', channel, 'i_set', 0) + self.client_watcher.report_update.disconnect(self.autotune_tick) + self.params[channel].child('PID Config', 'PID Auto Tune', 'Run', 'Autotuning...').setValue(0) + @pyqtSlot(list) def update_pid(self, pid_settings): for settings in pid_settings: