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 ]; 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 { thermostat_gui = pkgs.python3Packages.buildPythonPackage {
pname = "thermostat_gui"; pname = "thermostat_gui";
version = "0.0.0"; version = "0.0.0";
src = "${self}/pytec"; src = "${self}/pytec";
nativeBuildInputs = [ pkgs.qt6.wrapQtAppsHook ]; 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; dontWrapQtApps = true;
postFixup = '' postFixup = ''
@ -100,7 +123,7 @@
buildInputs = with pkgs; [ buildInputs = with pkgs; [
rust openocd dfu-util rust openocd dfu-util
] ++ (with python3Packages; [ ] ++ (with python3Packages; [
numpy matplotlib pyqtgraph setuptools pyqt6 qasync numpy matplotlib pyqtgraph setuptools pyqt6 qasync pglive
]); ]);
}; };
defaultPackage.x86_64-linux = thermostat; defaultPackage.x86_64-linux = thermostat;

View File

@ -1,8 +1,12 @@
from PyQt6 import QtWidgets, QtGui from PyQt6 import QtWidgets, QtGui
from PyQt6.QtCore import pyqtSignal, QObject, QSignalBlocker, pyqtSlot from PyQt6.QtCore import pyqtSignal, QObject, QSignalBlocker, pyqtSlot
from pyqtgraph import PlotWidget
from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType
import pyqtgraph as pg 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 sys
import argparse import argparse
import logging import logging
@ -126,11 +130,17 @@ class ClientWatcher(QObject):
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""The maximum number of sample points to store."""
DEFAULT_MAX_SAMPLES = 1000
def __init__(self, args): def __init__(self, args):
super().__init__() super().__init__()
self.setupUi(self) self.setupUi(self)
self.max_samples = self.DEFAULT_MAX_SAMPLES
self._set_up_context_menu() self._set_up_context_menu()
self.fan_power_slider.valueChanged.connect(self.fan_set) self.fan_power_slider.valueChanged.connect(self.fan_set)
@ -138,11 +148,24 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._set_param_tree() 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.fan_pwm_recommended = False
self.tec_client = Client() self.tec_client = Client()
self.client_watcher = ClientWatcher(self, self.tec_client, self.report_refresh_spin.value()) 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.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.report_update.connect(self.update_report)
self.client_watcher.pid_update.connect(self.update_pid) self.client_watcher.pid_update.connect(self.update_pid)
self.client_watcher.pwm_update.connect(self.update_pwm) 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) 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): async def _on_connection_changed(self, result):
self.graph_group.setEnabled(result) self.graph_group.setEnabled(result)
self.fan_group.setEnabled(result) self.fan_group.setEnabled(result)
@ -191,6 +239,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.status_lbl.setText("Disconnected") self.status_lbl.setText("Disconnected")
self.fan_pwm_warning.setPixmap(QtGui.QPixmap()) self.fan_pwm_warning.setPixmap(QtGui.QPixmap())
self.fan_pwm_warning.setToolTip("") self.fan_pwm_warning.setToolTip("")
self.clear_graphs()
self.client_watcher.stop_watching() self.client_watcher.stop_watching()
def _set_fan_pwm_warning(self): def _set_fan_pwm_warning(self):
@ -270,6 +319,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
await self._on_connection_changed(False) await self._on_connection_changed(False)
await self.tec_client.disconnect() 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) @asyncSlot(object, object)
async def send_command(self, param, changes): async def send_command(self, param, changes):
for param, change, data in changes: for param, change, data in changes:

View File

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

View File

@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'tec_qt.ui' # 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 # 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. # 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.setHeaderHidden(True)
self.ch0_tree.setObjectName("ch0_tree") self.ch0_tree.setObjectName("ch0_tree")
self.graphs_layout.addWidget(self.ch0_tree, 0, 0, 1, 1) 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.ch1_t_graph.setObjectName("ch1_t_graph")
self.graphs_layout.addWidget(self.ch1_t_graph, 1, 1, 1, 1) 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.ch0_t_graph.setObjectName("ch0_t_graph")
self.graphs_layout.addWidget(self.ch0_t_graph, 0, 1, 1, 1) 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.ch0_i_graph.setObjectName("ch0_i_graph")
self.graphs_layout.addWidget(self.ch0_i_graph, 0, 2, 1, 1) 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.ch1_i_graph.setObjectName("ch1_i_graph")
self.graphs_layout.addWidget(self.ch1_i_graph, 1, 2, 1, 1) self.graphs_layout.addWidget(self.ch1_i_graph, 1, 2, 1, 1)
self.graphs_layout.setColumnMinimumWidth(0, 100) self.graphs_layout.setColumnMinimumWidth(0, 100)
@ -306,7 +306,7 @@ class Ui_MainWindow(object):
self.report_refresh_spin.setSuffix(_translate("MainWindow", " s")) self.report_refresh_spin.setSuffix(_translate("MainWindow", " s"))
self.report_box.setText(_translate("MainWindow", "Report")) self.report_box.setText(_translate("MainWindow", "Report"))
self.report_apply_btn.setText(_translate("MainWindow", "Apply")) 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 from pyqtgraph.parametertree import ParameterTree