forked from M-Labs/thermostat
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:
parent
90df3ae784
commit
fdf4c4f0d6
27
flake.nix
27
flake.nix
|
@ -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;
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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/>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue