diff --git a/flake.nix b/flake.nix
index ccc5ad8..5c80712 100644
--- a/flake.nix
+++ b/flake.nix
@@ -68,13 +68,36 @@
propagatedBuildInputs = [ pkgs.python3Packages.pyqt6 ];
};
+ pyqtgraph = pkgs.python3Packages.buildPythonPackage rec {
+ pname = "pyqtgraph";
+ version = "0.13.3";
+ format = "pyproject";
+ src = pkgs.fetchPypi {
+ inherit pname version;
+ sha256 = "sha256-WBCNhBHHBU4IQdi3ke6F4QH8KWubNZwOAd3jipj/Ks4=";
+ };
+ propagatedBuildInputs = with pkgs.python3Packages; [ numpy pyqt6 ];
+ };
+
+ pglive = pkgs.python3Packages.buildPythonPackage rec {
+ pname = "pglive";
+ version = "0.7.2";
+ format = "pyproject";
+ src = pkgs.fetchPypi {
+ inherit pname version;
+ sha256 = "sha256-jqj8X6H1N5mJQ4OrY5ANqRB0YJByqg/bNneEALWmH1A=";
+ };
+ buildInputs = [ pkgs.python3Packages.poetry-core ];
+ propagatedBuildInputs = [ pyqtgraph pkgs.python3Packages.numpy ];
+ };
+
thermostat_gui = pkgs.python3Packages.buildPythonPackage {
pname = "thermostat_gui";
version = "0.0.0";
src = "${self}/pytec";
nativeBuildInputs = [ pkgs.qt6.wrapQtAppsHook ];
- propagatedBuildInputs = [ pkgs.qt6.qtbase ] ++ (with pkgs.python3Packages; [ pyqtgraph pyqt6 qasync ]);
+ propagatedBuildInputs = [ pkgs.qt6.qtbase ] ++ (with pkgs.python3Packages; [ pyqtgraph pyqt6 qasync pglive ]);
dontWrapQtApps = true;
postFixup = ''
@@ -100,7 +123,7 @@
buildInputs = with pkgs; [
rust openocd dfu-util
] ++ (with python3Packages; [
- numpy matplotlib pyqtgraph setuptools pyqt6 qasync
+ numpy matplotlib pyqtgraph setuptools pyqt6 qasync pglive
]);
};
defaultPackage.x86_64-linux = thermostat;
diff --git a/pytec/tec_qt.py b/pytec/tec_qt.py
index ec3be67..bfa362b 100644
--- a/pytec/tec_qt.py
+++ b/pytec/tec_qt.py
@@ -1,8 +1,12 @@
from PyQt6 import QtWidgets, QtGui
from PyQt6.QtCore import pyqtSignal, QObject, QSignalBlocker, pyqtSlot
-from pyqtgraph import PlotWidget
from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType
import pyqtgraph as pg
+from pglive.sources.data_connector import DataConnector
+from pglive.kwargs import Axis
+from pglive.sources.live_plot import LiveLinePlot
+from pglive.sources.live_plot_widget import LivePlotWidget
+from pglive.sources.live_axis import LiveAxis
import sys
import argparse
import logging
@@ -126,11 +130,17 @@ class ClientWatcher(QObject):
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
+
+ """The maximum number of sample points to store."""
+ DEFAULT_MAX_SAMPLES = 1000
+
def __init__(self, args):
super().__init__()
self.setupUi(self)
+ self.max_samples = self.DEFAULT_MAX_SAMPLES
+
self._set_up_context_menu()
self.fan_power_slider.valueChanged.connect(self.fan_set)
@@ -138,11 +148,24 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._set_param_tree()
+ self.ch0_t_plot = LiveLinePlot()
+ self.ch0_i_plot = LiveLinePlot()
+ self.ch1_t_plot = LiveLinePlot()
+ self.ch1_i_plot = LiveLinePlot()
+
+ self._set_up_graphs()
+
+ self.ch0_t_connector = DataConnector(self.ch0_t_plot, max_points=self.DEFAULT_MAX_SAMPLES)
+ self.ch0_i_connector = DataConnector(self.ch0_i_plot, max_points=self.DEFAULT_MAX_SAMPLES)
+ self.ch1_t_connector = DataConnector(self.ch1_t_plot, max_points=self.DEFAULT_MAX_SAMPLES)
+ self.ch1_i_connector = DataConnector(self.ch1_i_plot, max_points=self.DEFAULT_MAX_SAMPLES)
+
self.fan_pwm_recommended = False
self.tec_client = Client()
self.client_watcher = ClientWatcher(self, self.tec_client, self.report_refresh_spin.value())
self.client_watcher.fan_update.connect(self.fan_update)
+ self.client_watcher.report_update.connect(self.plot)
self.client_watcher.report_update.connect(self.update_report)
self.client_watcher.pid_update.connect(self.update_pid)
self.client_watcher.pwm_update.connect(self.update_pwm)
@@ -175,6 +198,31 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.thermostat_settings.setMenu(self.menu)
+ def _set_up_graphs(self):
+ for graph in self.ch0_t_graph, self.ch0_i_graph, self.ch1_t_graph, self.ch1_i_graph:
+ time_axis = LiveAxis('bottom', text="Time since Thermostat reset", **{Axis.TICK_FORMAT: Axis.DURATION})
+ time_axis.showLabel()
+ graph.setAxisItems({'bottom': time_axis})
+
+ for graph in self.ch0_t_graph, self.ch1_t_graph:
+ temperature_axis = LiveAxis('left', text="Temperature", units="°C")
+ temperature_axis.showLabel()
+ graph.setAxisItems({'left': temperature_axis})
+
+ for graph in self.ch0_i_graph, self.ch1_i_graph:
+ current_axis = LiveAxis('left', text="Current", units="A")
+ current_axis.showLabel()
+ graph.setAxisItems({'left': current_axis})
+
+ self.ch0_t_graph.addItem(self.ch0_t_plot)
+ self.ch0_i_graph.addItem(self.ch0_i_plot)
+ self.ch1_t_graph.addItem(self.ch1_t_plot)
+ self.ch1_i_graph.addItem(self.ch1_i_plot)
+
+ def clear_graphs(self):
+ for connector in self.ch0_t_connector, self.ch0_i_connector, self.ch1_t_connector, self.ch1_i_connector:
+ connector.clear()
+
async def _on_connection_changed(self, result):
self.graph_group.setEnabled(result)
self.fan_group.setEnabled(result)
@@ -191,6 +239,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.status_lbl.setText("Disconnected")
self.fan_pwm_warning.setPixmap(QtGui.QPixmap())
self.fan_pwm_warning.setToolTip("")
+ self.clear_graphs()
self.client_watcher.stop_watching()
def _set_fan_pwm_warning(self):
@@ -270,6 +319,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
await self._on_connection_changed(False)
await self.tec_client.disconnect()
+ @pyqtSlot(list)
+ def plot(self, report):
+ for channel in range(2):
+ temperature = report[channel]['temperature']
+ current = report[channel]['tec_i']
+ time = report[channel]['time']
+
+ if temperature is not None and current is not None:
+ getattr(self, f'ch{channel}_t_connector').cb_append_data_point(temperature, time)
+ getattr(self, f'ch{channel}_i_connector').cb_append_data_point(current, time)
+
@asyncSlot(object, object)
async def send_command(self, param, changes):
for param, change, data in changes:
diff --git a/pytec/tec_qt.ui b/pytec/tec_qt.ui
index 6cda65f..73533f6 100644
--- a/pytec/tec_qt.ui
+++ b/pytec/tec_qt.ui
@@ -104,28 +104,28 @@
-
-
+
Channel 1 Temperature
-
-
+
Channel 0 Temperature
-
-
+
Channel 0 Current
-
-
+
Channel 1 Current
@@ -656,18 +656,18 @@
-
- PlotWidget
- QWidget
-
- 1
-
ParameterTree
QWidget
1
+
+ LivePlotWidget
+ QWidget
+ pglive.sources.live_plot_widget
+ 1
+
diff --git a/pytec/ui_tec_qt.py b/pytec/ui_tec_qt.py
index 96cee43..40f54e6 100644
--- a/pytec/ui_tec_qt.py
+++ b/pytec/ui_tec_qt.py
@@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'tec_qt.ui'
#
-# Created by: PyQt6 UI code generator 6.4.2
+# Created by: PyQt6 UI code generator 6.5.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
@@ -52,16 +52,16 @@ class Ui_MainWindow(object):
self.ch0_tree.setHeaderHidden(True)
self.ch0_tree.setObjectName("ch0_tree")
self.graphs_layout.addWidget(self.ch0_tree, 0, 0, 1, 1)
- self.ch1_t_graph = PlotWidget(parent=self.graph_group)
+ self.ch1_t_graph = LivePlotWidget(parent=self.graph_group)
self.ch1_t_graph.setObjectName("ch1_t_graph")
self.graphs_layout.addWidget(self.ch1_t_graph, 1, 1, 1, 1)
- self.ch0_t_graph = PlotWidget(parent=self.graph_group)
+ self.ch0_t_graph = LivePlotWidget(parent=self.graph_group)
self.ch0_t_graph.setObjectName("ch0_t_graph")
self.graphs_layout.addWidget(self.ch0_t_graph, 0, 1, 1, 1)
- self.ch0_i_graph = PlotWidget(parent=self.graph_group)
+ self.ch0_i_graph = LivePlotWidget(parent=self.graph_group)
self.ch0_i_graph.setObjectName("ch0_i_graph")
self.graphs_layout.addWidget(self.ch0_i_graph, 0, 2, 1, 1)
- self.ch1_i_graph = PlotWidget(parent=self.graph_group)
+ self.ch1_i_graph = LivePlotWidget(parent=self.graph_group)
self.ch1_i_graph.setObjectName("ch1_i_graph")
self.graphs_layout.addWidget(self.ch1_i_graph, 1, 2, 1, 1)
self.graphs_layout.setColumnMinimumWidth(0, 100)
@@ -306,7 +306,7 @@ class Ui_MainWindow(object):
self.report_refresh_spin.setSuffix(_translate("MainWindow", " s"))
self.report_box.setText(_translate("MainWindow", "Report"))
self.report_apply_btn.setText(_translate("MainWindow", "Apply"))
-from pyqtgraph import PlotWidget
+from pglive.sources.live_plot_widget import LivePlotWidget
from pyqtgraph.parametertree import ParameterTree