Plot temperature and current graphs

- Have units

- Samples are limited

- pglive is used for better live graphs
-- Also fixes bug with constantly updating normal pyqtgraphs where it
will bug out if right-clicked on and context menu is brought up
--Since pglive requires pyqtgraph == 0.13.3, upgrade pyqtgraph to that
too.
This commit is contained in:
atse 2023-07-06 16:06:33 +08:00
parent 90df3ae784
commit fdf4c4f0d6
4 changed files with 102 additions and 19 deletions

View File

@ -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;

View File

@ -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:

View File

@ -104,28 +104,28 @@
</widget>
</item>
<item row="1" column="1">
<widget class="PlotWidget" name="ch1_t_graph" native="true">
<widget class="LivePlotWidget" name="ch1_t_graph" native="true">
<property name="title">
<string>Channel 1 Temperature</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="PlotWidget" name="ch0_t_graph" native="true">
<widget class="LivePlotWidget" name="ch0_t_graph" native="true">
<property name="title">
<string>Channel 0 Temperature</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="PlotWidget" name="ch0_i_graph" native="true">
<widget class="LivePlotWidget" name="ch0_i_graph" native="true">
<property name="title">
<string>Channel 0 Current</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="PlotWidget" name="ch1_i_graph" native="true">
<widget class="LivePlotWidget" name="ch1_i_graph" native="true">
<property name="title">
<string>Channel 1 Current</string>
</property>
@ -656,18 +656,18 @@
</widget>
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<extends>QWidget</extends>
<header>pyqtgraph</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ParameterTree</class>
<extends>QWidget</extends>
<header>pyqtgraph.parametertree</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LivePlotWidget</class>
<extends>QWidget</extends>
<header>pglive.sources.live_plot_widget</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@ -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