diff --git a/src/UM24CUI.py b/src/UM24CUI.py index efea25a..90754d6 100644 --- a/src/UM24CUI.py +++ b/src/UM24CUI.py @@ -48,13 +48,14 @@ class Ui_MainWindow(object): self.horizontalLayout_2 = QHBoxLayout(self.frame) self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") self.comboDevices = QComboBox(self.frame) + self.comboDevices.setEnabled(False) self.comboDevices.setObjectName(u"comboDevices") self.horizontalLayout_2.addWidget(self.comboDevices) self.buttonConnection = QPushButton(self.frame) self.buttonConnection.setObjectName(u"buttonConnection") - self.buttonConnection.setFlat(False) + self.buttonConnection.setEnabled(False) self.horizontalLayout_2.addWidget(self.buttonConnection) diff --git a/src/meter/um24c.py b/src/meter/um24c.py index e4c35ec..de574c9 100644 --- a/src/meter/um24c.py +++ b/src/meter/um24c.py @@ -128,16 +128,19 @@ def is_connected(): return False -def get_meter_stats(): +def get_meter_stats(timeout=50): global rfcomm rfcomm.send(UM24C_GET_STATUS) - sleep(0.25) + sleep(timeout) data = rfcomm.recv(0x8f) - assert len(data) == 130 - assert data[0:2] == PACKET_MAGIC[0] and data[-2:] == PACKET_MAGIC[1] - values = list(unpack(DATA_FORMAT, data)) - for i, value in enumerate(values): - if type(value) is bytes: - values[i] = int.from_bytes(value.strip(b'\x00'), 'big', - signed=False) - return UMeterStatus(*values) + # assert len(data) == 130 + # assert data[0:2] == PACKET_MAGIC[0] and data[-2:] == PACKET_MAGIC[1] + try: + values = list(unpack(DATA_FORMAT, data)) + for i, value in enumerate(values): + if type(value) is bytes: + values[i] = int.from_bytes(value.strip(b'\x00'), 'big', + signed=False) + return UMeterStatus(*values) + except: + return None diff --git a/src/um24clab.py b/src/um24clab.py index a9c93a4..644ccb0 100644 --- a/src/um24clab.py +++ b/src/um24clab.py @@ -1,9 +1,10 @@ #!/usr/bin/env python import sys +import traceback import random from PySide2.QtCore import ( - Qt, QTimer + Qt, QTimer, QObject, Signal, Slot, QRunnable, QThreadPool ) from PySide2.QtWidgets import ( QApplication, QMainWindow @@ -17,6 +18,89 @@ from UM24CUI import Ui_MainWindow from meter import um24c +class WorkerSignals(QObject): + """ + Defines the signals available from a running worker thread. + + Supported signals are: + + finished + No data + + error + `tuple` (exctype, value, traceback.format_exc() ) + + result + `object` data returned from processing, anything + + progress + `int` indicating % progress + """ + finished = Signal() + error = Signal(tuple) + result = Signal(object) + progress = Signal(int) + + +class MeterSignals(WorkerSignals): + + progress = Signal(object) + + +class UM24LabUIWorker(QRunnable): + + def __init__(self, fn, *args, **kwargs): + super(UM24LabUIWorker, self).__init__() + + self.fn = fn + self.args = args + self.kwargs = kwargs + self.signals = WorkerSignals() + + self.kwargs['progress_callback'] = self.signals.progress + + @Slot() + def run(self): + try: + result = self.fn(*self.args, **self.kwargs) + except: + traceback.print_exc() + exctype, value = sys.exc_info()[:2] + self.signals.error.emit((exctype, value, traceback.format_exc())) + else: + self.signals.result.emit(result) + finally: + self.signals.finished.emit() + + +class UM24LabWorker(UM24LabUIWorker): + + def __init__(self, fn, *args, **kwargs): + super(UM24LabUIWorker, self).__init__() + + self.fn = fn + self.args = args + self.kwargs = kwargs + self.signals = MeterSignals() + + self.kwargs['progress_callback'] = self.signals.progress + + @Slot() + def run(self): + while um24c.is_connected(): + try: + result = self.fn(*self.args, **self.kwargs) + except: + traceback.print_exc() + exctype, value = sys.exc_info()[:2] + self.signals.error.emit((exctype, value, traceback.format_exc())) + else: + self.signals.result.emit(result) + finally: + self.signals.finished.emit() + + + class UM24Lab(QMainWindow): def __init__(self): @@ -24,19 +108,37 @@ class UM24Lab(QMainWindow): self.ui = Ui_MainWindow() self.ui.setupUi(self) + self.threadpool = QThreadPool() + + self.meter_stats = None + + self.ui.labelTimeRecorded.setText('00:00:00') + self.ui.sliderBrightness.setValue(0) + self.ui.sliderScreenOff.setValue(0) + self.ui.lcdVoltage.display(0.00) + self.ui.lcdCurrent.display(0.00) + self.ui.lcdImpedance.display(0.00) + self.ui.lcdPower.display(0.00) + self.ui.lcdTempC.display(0) + self.ui.lcdTempF.display(0) + self.ui.progressTempC.setValue(0) + self.ui.progressTempF.setValue(0) + self.ui.lcdDataMinus.display(0.00) + self.ui.lcdDataPlus.display(0.00) + self.ui.lcdCapacityRecorded.display(0) + self.ui.lcdEnergyRecorded.display(0) + self.ui.spinStopCurrent.setValue(0.00) + self.ui.buttonGroup.buttons()[0].setChecked(True) + # TODO: Add splash screen, discover in background - self.connected = False - supported_devices = um24c.discover_devices() - for device in supported_devices: - self.ui.comboDevices.addItem(device[0]) - self.ui.buttonConnection.clicked.connect(self.connect_device) + self.action_discover_devices() # --- Plot setup --- self._setup_plots() self.timer_step = 0 timer = QTimer(self) timer.timeout.connect(self.update_graphs) - timer.start(200) + timer.start(50) def _setup_plots(self): self.ui.plotVoltage = QtCharts.QChart() @@ -89,40 +191,88 @@ class UM24Lab(QMainWindow): self.current.addAxis(axis_y, Qt.AlignLeft) self.current_series.attachAxis(axis_y) - def connect_device(self): - if not self.connected: + def do_discover_devices(self, progress_callback): + return um24c.discover_devices() + + def discover_devices_finished(self): + if self.ui.comboDevices.count() > 0: + self.ui.comboDevices.setEnabled(True) + self.ui.buttonConnection.setEnabled(True) + + def discover_devices_result(self, devices): + for device in devices: + self.ui.comboDevices.addItem(device[0]) + self.ui.buttonConnection.clicked.connect(self.action_connect_device) + + def action_discover_devices(self): + worker = UM24LabUIWorker(self.do_discover_devices) + worker.signals.result.connect(self.discover_devices_result) + worker.signals.finished.connect(self.discover_devices_finished) + # worker.signals.progress.connect(...) + self.threadpool.start(worker) + + def do_connect_device(self, progress_callback): + if not um24c.is_connected(): um24c.connect(self.ui.comboDevices.currentText()) if um24c.is_connected(): self.ui.buttonConnection.setText('Disconnect') self.ui.comboDevices.setEnabled(False) - self.connected = True + return True else: um24c.disconnect() if not um24c.is_connected(): self.ui.buttonConnection.setText('Connect') self.ui.comboDevices.setEnabled(True) - self.connected = False + return False + + def connect_device_result(self, status): + self.ui.comboDevices.setEnabled(not status) + if status: + self.ui.buttonConnection.setText('Disconnect') + self.action_update_stats() + else: + self.ui.buttonConnection.setText('Connect') + + def action_connect_device(self): + worker = UM24LabUIWorker(self.do_connect_device) + worker.signals.result.connect(self.connect_device_result) + # worker.signals.finished.connect(...) + # worker.signals.progress.connect(...) + self.threadpool.start(worker) + + def do_update_stats(self, progress_callback): + while um24c.is_connected: + current = um24c.get_meter_stats() + progress_callback.emit(current) + + def update_stats_progress(self, current): + self.meter_stats = current + + def action_update_stats(self): + worker = UM24LabWorker(self.do_update_stats) + worker.signals.progress.connect(self.update_stats_progress) + self.threadpool.start(worker) def update_graphs(self): - if um24c.is_connected(): - self.meter_stats = um24c.get_meter_stats() - print(self.meter_stats) - - self.ui.lcdVoltage.display(self.meter_stats.Voltage / 100) - self.ui.lcdCurrent.display(self.meter_stats.Current / 100) - self.ui.lcdImpedance.display(self.meter_stats.Load_equivalent_impedance - / 10) - self.ui.lcdPower.display(self.meter_stats.Power / 100) - self.ui.lcdTempC.display(self.meter_stats.Temperature_Celcius) - self.ui.lcdTempF.display(self.meter_stats.Temperature_Fahrenheit) - # self.ui.progressTempC.setValue(self.meter_stats.Temperature_Celcius) - # self.ui.progressTempF.setValue(self.meter_stats.Temperature_Fahrenheit) - self.ui.lcdDataMinus.display(self.meter_stats.DMinus / 100) - self.ui.lcdDataPlus.display(self.meter_stats.DPlus / 100) - self.ui.lcdCapacityRecorded.display(self.meter_stats.Recorded_Capacity) - self.ui.lcdEnergyRecorded.display(self.meter_stats.Recorded_Energy) - self.ui.spinStopCurrent.setValue(self.meter_stats.Current_Trigger / - 100) + if um24c.is_connected() and self.meter_stats: + ui = self.ui + ui.lcdVoltage.display(self.meter_stats.Voltage / 100) + ui.lcdCurrent.display(self.meter_stats.Current / 100) + ui.lcdImpedance.display(self.meter_stats.Load_equivalent_impedance + / 10) + ui.lcdPower.display(self.meter_stats.Power / 100) + ui.lcdTempC.display(self.meter_stats.Temperature_Celcius) + ui.lcdTempF.display(self.meter_stats.Temperature_Fahrenheit) + ui.progressTempC.setValue(self.meter_stats.Temperature_Celcius) + ui.progressTempF.setValue(self.meter_stats.Temperature_Fahrenheit) + ui.lcdDataMinus.display(self.meter_stats.DMinus / 100) + ui.lcdDataPlus.display(self.meter_stats.DPlus / 100) + ui.lcdCapacityRecorded.display(self.meter_stats.Recorded_Capacity) + ui.lcdEnergyRecorded.display(self.meter_stats.Recorded_Energy) + ui.spinStopCurrent.setValue(self.meter_stats.Current_Trigger / 100) + ui.sliderBrightness.setValue(self.meter_stats.Display_brightness) + ui.sliderScreenOff.setValue(self.meter_stats.Display_timeout) + ui.buttonGroup.buttons()[self.meter_stats.Current_screen].setChecked(True) # self.voltage_series.append(self.timer_step, random.randint(0, 10)) # self.current_series.append(self.timer_step, random.randint(0, 10))