Index: simulator/run.py =================================================================== diff -u -refb0dcc34f39f35d41f43cca9667db17fd97c844 -ra3c7851a7de7cbed6b06abef5033b35308c04c93 --- simulator/run.py (.../run.py) (revision efb0dcc34f39f35d41f43cca9667db17fd97c844) +++ simulator/run.py (.../run.py) (revision a3c7851a7de7cbed6b06abef5033b35308c04c93) @@ -1,542 +1,30 @@ -# import system modules +""" +########################################################################### +# +# Copyright (c) 2020-2026 Diality Inc. - All Rights Reserved. +# +# THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN +# WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. +# +# @file run.py +# +# @author (last) Behrouz NematiPour +# @date (last) 24-Feb-2021 +# @author (original) Behrouz NematiPour +# @date (original) 19-Aug-2020 +# +############################################################################ +the main function +to load the main simulator loader widget +""" import sys - -# import project classes -# import names -from dialin.squish import utils, denaliMessages -from dialin.squish.denaliMessages import txStates, EResponse -# Import PySide2 classes -from PySide2.QtUiTools import QUiLoader +from dialin.ui import utils from PySide2 import QtCore, QtWidgets -from PySide2.QtCore import QFile, Slot -from PySide2.QtGui import qApp -from PySide2.QtCore import QTimer -from dialin.common import Ranges +from simulator.loader import Simulator +from dialin.ui.hd_simulator import HDSimulator +from dialin.ui.hd_simulator_alarms import HDAlarmsSimulator, HIGH -class RunTimeWidget: - """ - the parent class of all the run time loadable widgets - """ - - loader: QUiLoader - window: QtWidgets.QWidget - - def __init__(self, ui_name: str): - self.ui_name = ui_name - self.loader = QUiLoader() - self.__load_ui() - - def __load_ui(self): - """ - loads the ui file of the GUI at run time - :return: none - """ - ui_file = QFile(self.ui_name) - ui_file.open(QFile.ReadOnly) - self.window = self.loader.load(ui_file) - # assert if the ui file can't be loaded - error = self.loader.errorString() - assert len(error) == 0, error - ui_file.close() - - def show(self): - """ - shows the main container window - :return: none - """ - self.window.show() - - def __find_child(self, child_type, child_name: str) -> QtWidgets.QWidget: - """ - finds a child in the loaded ui and returns the reference to it if found - otherwise quits the application - :param child_type: (var) type of the child - :param child_name: (str) name of the child - :return: (QtWidgets.QWidget) reference to the child - """ - child: QtWidgets.QWidget = self.window.findChild(child_type, child_name) - assert child is not None, "child name '{}' with type '{}' can't be found.".format(child_name, child_type) - return child - - def find_label(self, name: str) -> QtWidgets.QLabel: - """ - convenient method of find_child for QLabel - :param name: (str) name of the QLabel Object - :return: (QLabel) reference to the QLabel - """ - child = self.__find_child(QtWidgets.QLabel, name) - return child - - def find_button(self, name: str) -> QtWidgets.QPushButton: - """ - convenient method of find_child for QPushButton - :param name: (str) name of the QPushButton Object - :return: (QPushButton) reference to the QPushButton - """ - child = self.__find_child(QtWidgets.QPushButton, name) - return child - - def find_combobox(self, name: str) -> QtWidgets.QComboBox: - """ - convenient method of find_child for QComboBox - :param name: (str) name of the QComboBox Object - :return: (QComboBox) reference to the QComboBox - """ - child = self.__find_child(QtWidgets.QComboBox, name) - return child - - def find_checkox(self, name: str) -> QtWidgets.QCheckBox: - """ - convenient method of find_child for QCheckBox - :param name: (str) name of the QCheckBox Object - :return: (QCheckBox) reference to the QComboBox - """ - child = self.__find_child(QtWidgets.QCheckBox, name) - return child - - def find_spinbox(self, name: str) -> QtWidgets.QSpinBox: - """ - convenient method of find_child for QSpinBox - :param name: (str) name of the QSpinBox Object - :return: (QSpinBox) reference to the QSpinBox - """ - child = self.__find_child(QtWidgets.QSpinBox, name) - return child - - def find_slider(self, name: str) -> QtWidgets.QSlider: - """ - convenient method of find_child for QSlider - :param name: (str) name of the QSlider Object - :return: (QSlider) reference to the QSlider - """ - child = self.__find_child(QtWidgets.QSlider, name) - return child - - def find_table_widget(self, name: str) -> QtWidgets.QTableWidget: - """ - convenient method of find_child for QTableWidget - :param name: (str) name of the QTableWidget Object - :return: (QTableWidget) reference to the QTableWidget - """ - child: QtWidgets.QTableWidget = self.__find_child(QtWidgets.QTableWidget, name) - return child - - -class Simulator(RunTimeWidget): - """ - The simulator class which loads the ui file dynamically and initializes the objects - and can be eventually shown. - Note: this class is growing fast and seems like needs to be multiple classes - """ - # global variables declarations - ui_file_name = "Simulator.ui" - timer: QTimer - # checkbox - chkRangesBroadcast: QtWidgets.QCheckBox - # pushbutton - btnSalineAccept: QtWidgets.QPushButton - btnSalineReject: QtWidgets.QPushButton - btnUfPauseAccept: QtWidgets.QPushButton - btnUfPauseReject: QtWidgets.QPushButton - btnUfResumeAccept: QtWidgets.QPushButton - btnUfResumeReject: QtWidgets.QPushButton - btnUfEditAccept: QtWidgets.QPushButton - btnUfEditReject: QtWidgets.QPushButton - btnAVPressuresAccept: QtWidgets.QPushButton - btnAVPressuresReject: QtWidgets.QPushButton - # label - lblSalineAction: QtWidgets.QLabel - lblUfPauseAction: QtWidgets.QLabel - lblUfResumeAction: QtWidgets.QLabel - lblUfEditAction: QtWidgets.QLabel - lblArterialTitle: QtWidgets.QLabel - lblVenousTitle: QtWidgets.QLabel - lblAVPressuresAction: QtWidgets.QLabel - lblArterialLimitLow: QtWidgets.QLabel - lblArterialLimitHigh: QtWidgets.QLabel - lblVenousLimitLow: QtWidgets.QLabel - lblVenousLimitHigh: QtWidgets.QLabel - # spinbox - spnSalineRejectReason: QtWidgets.QSpinBox - spnUfPauseRejectReason: QtWidgets.QSpinBox - spnUfResumeRejectReason: QtWidgets.QSpinBox - spnUfEditRejectReason: QtWidgets.QSpinBox - spnAVPressuresRejectReason: QtWidgets.QSpinBox - spnDurationMin: QtWidgets.QSpinBox - spnDurationMax: QtWidgets.QSpinBox - spnUFVolumeMin: QtWidgets.QSpinBox - spnUFVolumeMax: QtWidgets.QSpinBox - spnDialysateMin: QtWidgets.QSpinBox - spnDialysateMax: QtWidgets.QSpinBox - spnArterialLimitLow: QtWidgets.QSpinBox - spnArterialLimitHigh: QtWidgets.QSpinBox - spnVenousLimitLow: QtWidgets.QSpinBox - spnVenousLimitHigh: QtWidgets.QSpinBox - # combobox - cmbSalineAcceptTarget: QtWidgets.QComboBox - # sliders - sldSalineTarget: QtWidgets.QSlider - sldSalineCumulative: QtWidgets.QSlider - sldSalineVolume: QtWidgets.QSlider - sldUfVolume: QtWidgets.QSlider - sldArterialValue: QtWidgets.QSlider - sldVenousValue: QtWidgets.QSlider - # tables - tblSalineSubMode: QtWidgets.QTableWidget - tblSalineUFStates: QtWidgets.QTableWidget - tblSalineSalineStates: QtWidgets.QTableWidget - - # static class variables - saline_requested_state: txStates - - def __init__(self): - super().__init__(Simulator.ui_file_name) - self.saline_requested_state = txStates.SALINE_BOLUS_STATE_IDLE - self.initialize() - - def setup_ranges(self): - """ - sets up the treatment ranges timer and 1 sec interval - :return: none - """ - self.spnDurationMin = self.find_spinbox('spnDurationMin') - self.spnDurationMax = self.find_spinbox('spnDurationMax') - self.spnUFVolumeMin = self.find_spinbox('spnUFVolumeMin') - self.spnUFVolumeMax = self.find_spinbox('spnUFVolumeMax') - self.spnDialysateMin = self.find_spinbox('spnDialysateMin') - self.spnDialysateMax = self.find_spinbox('spnDialysateMax') - self.chkRangesBroadcast = self.find_checkox('chkRangesBroadcast') - self.timer = QTimer() - self.timer.start(1000) - self.timer.timeout.connect(self.do_ranges_data) - - def setup_uf_adjustment(self): - """ - sets up the treatment Ultrafiltration adjustment GUI section - :return: none - """ - self.btnUfPauseAccept = self.find_button('btnUfPauseAccept') - self.btnUfPauseReject = self.find_button('btnUfPauseReject') - self.btnUfResumeAccept = self.find_button('btnUfResumeAccept') - self.btnUfResumeReject = self.find_button('btnUfResumeReject') - self.btnUfEditAccept = self.find_button('btnUfEditAccept') - self.btnUfEditReject = self.find_button('btnUfEditReject') - self.lblUfPauseAction = self.find_label('lblUfPauseAction') - self.lblUfResumeAction = self.find_label('lblUfResumeAction') - self.lblUfEditAction = self.find_label('lblUfEditAction') - self.spnUfPauseRejectReason = self.find_spinbox('spnUfPauseRejectReason') - self.spnUfResumeRejectReason = self.find_spinbox('spnUfResumeRejectReason') - self.spnUfEditRejectReason = self.find_spinbox('spnUfEditRejectReason') - self.sldUfVolume = self.find_slider('sldUfVolume') - self.btnUfPauseAccept.clicked.connect(self.do_uf_pause_accept) - self.btnUfPauseReject.clicked.connect(self.do_uf_pause_reject) - self.btnUfResumeAccept.clicked.connect(self.do_uf_resume_accept) - self.btnUfResumeReject.clicked.connect(self.do_uf_resume_reject) - self.btnUfEditAccept.clicked.connect(self.do_uf_edit_accept) - self.btnUfEditReject.clicked.connect(self.do_uf_edit_reject) - self.sldUfVolume.valueChanged.connect(self.do_uf_volume_data) - - def setup_saline_adjustment(self): - """ - sets up the treatment saline bolus adjustment GUI section - :return: none - """ - self.btnSalineAccept = self.find_button('btnSalineAccept') - self.btnSalineReject = self.find_button('btnSalineReject') - self.lblSalineAction = self.find_label('lblSalineAction') - self.spnSalineRejectReason = self.find_spinbox('spnSalineRejectReason') - self.cmbSalineAcceptTarget = self.find_combobox('cmbSalineAcceptTarget') - self.btnSalineAccept.clicked.connect(self.do_sb_accept) - self.btnSalineReject.clicked.connect(self.do_sb_reject) - - def setup_saline_data(self): - """ - sets up the treatment saline bolus data sender GUI section - :return: none - """ - self.sldSalineTarget = self.find_slider('sldSalineTarget') - self.sldSalineCumulative = self.find_slider('sldSalineCumulative') - self.sldSalineVolume = self.find_slider('sldSalineVolume') - self.sldSalineTarget.valueChanged.connect(self.do_saline_data) - self.sldSalineCumulative.valueChanged.connect(self.do_saline_data) - self.sldSalineVolume.valueChanged.connect(self.do_saline_data) - - def setup_treatment_states(self): - """ - sets up the treatment saline bolus states GUI section - :return: none - """ - self.tblSalineSubMode = self.find_table_widget('tblSalineSubMode') - self.tblSalineUFStates = self.find_table_widget('tblSalineUFStates') - self.tblSalineSalineStates = self.find_table_widget('tblSalineSalineStates') - self.tblSalineSubMode.setCurrentCell(0, 0) - self.tblSalineUFStates.setCurrentCell(0, 0) - self.tblSalineSalineStates.setCurrentCell(0, 0) - self.tblSalineSubMode.cellClicked.connect(self.do_saline_saline_state) - self.tblSalineUFStates.cellClicked.connect(self.do_saline_saline_state) - self.tblSalineSalineStates.cellClicked.connect(self.do_saline_saline_state) - - def setup_pressures_limits(self): - """ - sets up the treatment pressures - :return: none - """ - self.lblArterialTitle = self.find_label('lblArterialTitle') - self.lblVenousTitle = self.find_label('lblVenousTitle') - self.lblArterialLimitLow = self.find_label('lblArterialLimitLow') - self.lblArterialLimitHigh = self.find_label('lblArterialLimitHigh') - self.lblVenousLimitLow = self.find_label('lblVenousLimitLow') - self.lblVenousLimitHigh = self.find_label('lblVenousLimitHigh') - self.lblAVPressuresAction = self.find_label('lblAVPressuresAction') - - self.btnAVPressuresAccept = self.find_button('btnAVPressuresAccept') - self.btnAVPressuresReject = self.find_button('btnAVPressuresReject') - - self.spnArterialLimitLow = self.find_spinbox('spnArterialLimitLow') - self.spnArterialLimitHigh = self.find_spinbox('spnArterialLimitHigh') - self.spnVenousLimitLow = self.find_spinbox('spnVenousLimitLow') - self.spnVenousLimitHigh = self.find_spinbox('spnVenousLimitHigh') - self.spnAVPressuresRejectReason = self.find_spinbox('spnAVPressuresRejectReason') - - self.sldArterialValue = self.find_slider('sldArterialValue') - self.sldVenousValue = self.find_slider('sldVenousValue') - - self.lblArterialTitle.setText( - f"{self.lblArterialTitle.text()} [{Ranges.ARTERIAL_PRESSURE_MINIMUM},{Ranges.ARTERIAL_PRESSURE_MAXIMUM}]" - ) - self.lblVenousTitle.setText( - f"{self.lblVenousTitle.text()} [{Ranges.VENOUS_PRESSURE_MINIMUM},{Ranges.VENOUS_PRESSURE_MAXIMUM}]" - ) - - self.lblArterialLimitLow.setText(f"{Ranges.ARTERIAL_PRESSURE_LOW_MIN}\n{Ranges.ARTERIAL_PRESSURE_LOW_MAX}") - self.lblArterialLimitHigh.setText(f"{Ranges.ARTERIAL_PRESSURE_HIGH_MIN}\n{Ranges.ARTERIAL_PRESSURE_HIGH_MAX}") - self.lblVenousLimitLow.setText(f"{Ranges.VENOUS_PRESSURE_LOW_MIN}\n{Ranges.VENOUS_PRESSURE_LOW_MAX}") - self.lblVenousLimitHigh.setText(f"{Ranges.VENOUS_PRESSURE_HIGH_MIN}\n{Ranges.VENOUS_PRESSURE_HIGH_MAX}") - - # sending the CANBus message when slider value changed - self.sldArterialValue.valueChanged.connect(self.do_pressures_data) - self.sldVenousValue.valueChanged.connect(self.do_pressures_data) - - # sending the CANBus message when accepted or rejected - self.btnAVPressuresAccept.clicked.connect(self.do_pressures_limits_accept) - self.btnAVPressuresReject.clicked.connect(self.do_pressures_limits_reject) - - self.sldArterialValue.setMinimum(Ranges.ARTERIAL_PRESSURE_MINIMUM) - self.sldArterialValue.setMaximum(Ranges.ARTERIAL_PRESSURE_MAXIMUM) - self.sldArterialValue.setValue(0) - self.sldVenousValue.setMinimum(Ranges.VENOUS_PRESSURE_MINIMUM) - self.sldVenousValue.setMaximum(Ranges.VENOUS_PRESSURE_MAXIMUM) - self.sldVenousValue.setValue(0) - - self.spnArterialLimitLow.setMinimum(Ranges.ARTERIAL_PRESSURE_LOW_MIN) - self.spnArterialLimitLow.setMaximum(Ranges.ARTERIAL_PRESSURE_LOW_MAX) - self.spnArterialLimitLow.setValue(Ranges.ARTERIAL_PRESSURE_LOW_DEF) - - self.spnArterialLimitHigh.setMinimum(Ranges.ARTERIAL_PRESSURE_HIGH_MIN) - self.spnArterialLimitHigh.setMaximum(Ranges.ARTERIAL_PRESSURE_HIGH_MAX) - self.spnArterialLimitHigh.setValue(Ranges.ARTERIAL_PRESSURE_HIGH_DEF) - - self.spnVenousLimitLow.setMinimum(Ranges.VENOUS_PRESSURE_LOW_MIN) - self.spnVenousLimitLow.setMaximum(Ranges.VENOUS_PRESSURE_LOW_MAX) - self.spnVenousLimitLow.setValue(Ranges.VENOUS_PRESSURE_LOW_DEF) - - self.spnVenousLimitHigh.setMinimum(Ranges.VENOUS_PRESSURE_HIGH_MIN) - self.spnVenousLimitHigh.setMaximum(Ranges.VENOUS_PRESSURE_HIGH_MAX) - self.spnVenousLimitHigh.setValue(Ranges.VENOUS_PRESSURE_HIGH_DEF) - - @Slot() - def do_sb_accept(self): - """ - the slot for accept saline bolus button - :return: none - """ - # toggle the saline requested state - if self.saline_requested_state == txStates.SALINE_BOLUS_STATE_IN_PROGRESS: - self.saline_requested_state = txStates.SALINE_BOLUS_STATE_IDLE - else: - self.saline_requested_state = txStates.SALINE_BOLUS_STATE_IN_PROGRESS - - target = self.cmbSalineAcceptTarget.currentText() - denaliMessages.setSalineBolusResponse(True, 0, target, self.saline_requested_state) - self.lblSalineAction.setText('Accepted ' + target) - - @Slot() - def do_sb_reject(self): - """ - the slot for accept saline bolus button - :return: none - """ - reason = self.spnSalineRejectReason.value() - denaliMessages.setSalineBolusResponse(False, reason, 0, self.saline_requested_state) - self.lblSalineAction.setText('Rejected ' + "{}".format(reason)) - - @Slot() - def do_uf_pause_accept(self): - """ - the slot for accept ultrafiltration pause button - :return: none - """ - denaliMessages.setTreatmentAdjustUltrafiltrationStateResponse( - EResponse.Accepted, 0, txStates.UF_PAUSED_STATE) - self.lblUfPauseAction.setText('Accepted ') - - @Slot() - def do_uf_pause_reject(self): - """ - the slot for reject ultrafiltration pause button - :return: none - """ - reason = self.spnUfPauseRejectReason.value() - denaliMessages.setTreatmentAdjustUltrafiltrationStateResponse( - EResponse.Rejected, reason, txStates.UF_RUNNING_STATE) - self.lblUfPauseAction.setText('Rejected ' + "{}".format(reason)) - - @Slot() - def do_uf_resume_accept(self): - """ - the slot for accept ultrafiltration resume accept - :return: none - """ - denaliMessages.setTreatmentAdjustUltrafiltrationStateResponse( - EResponse.Accepted, 0, txStates.UF_RUNNING_STATE) - self.lblUfResumeAction.setText('Accepted ') - - @Slot() - def do_uf_resume_reject(self): - """ - the slot for reject ultrafiltration resume button - :return: none - """ - reason = self.spnUfResumeRejectReason.value() - denaliMessages.setTreatmentAdjustUltrafiltrationStateResponse( - EResponse.Rejected, reason, txStates.UF_PAUSED_STATE) - self.lblUfResumeAction.setText('Rejected ' + "{}".format(reason)) - - @Slot() - def do_uf_edit_accept(self): - """ - the slot for accept ultrafiltration next button - :return: none - """ - denaliMessages.setTreatmentAdjustUltrafiltrationEditResponse( - EResponse.Accepted, 0, 2500, 60, 0, 10, 0, 10) - self.lblUfEditAction.setText('Accepted ') - - @Slot() - def do_uf_edit_reject(self): - """ - the slot for reject ultrafiltration next button - :return: none - """ - reason = self.spnUfEditRejectReason.value() - denaliMessages.setTreatmentAdjustUltrafiltrationEditResponse( - EResponse.Rejected, reason, 2500, 60, 0, 10, 0, 10) - self.lblUfEditAction.setText('Rejected ' + "{}".format(reason)) - - @Slot() - def do_saline_saline_state(self): - """ - the slot for saline bolus state change - :return: none - """ - sub_mode = self.tblSalineSubMode.verticalHeaderItem(self.tblSalineSubMode.currentRow()).text() - uf_state = self.tblSalineUFStates.verticalHeaderItem(self.tblSalineUFStates.currentRow()).text() - saline = self.tblSalineSalineStates.verticalHeaderItem(self.tblSalineSalineStates.currentRow()).text() - denaliMessages.setTreatmentStatesData(sub_mode, uf_state, saline) - - @Slot() - def do_saline_data(self): - """ - the slot which is called to send the saline bolus data - by calling the denaliMessage API setTreatmentSalineBolusData - :return: none - """ - denaliMessages.setTreatmentSalineBolusData(self.sldSalineTarget.value(), - self.sldSalineCumulative.value(), - self.sldSalineVolume.value()) - - @Slot() - def do_uf_volume_data(self): - """ - sends the ultrafiltration delivered volume message - :return: none - """ - denaliMessages.setTreatmentUltrafiltration(self.sldUfVolume.value(), 0, 0, 0, 0, 0, 0) - - @Slot() - def do_ranges_data(self): - """ - sends the treatment ranges message with given value on the screen - :return: none - """ - if self.chkRangesBroadcast.isChecked(): - denaliMessages.setTreatmentParamRanges( - self.spnDurationMin.value(), - self.spnDurationMax.value(), - self.spnUFVolumeMin.value(), - self.spnUFVolumeMax.value(), - self.spnDialysateMin.value(), - self.spnDialysateMax.value() - ) - - @Slot() - def do_pressures_data(self): - """ - sends the pressures values message with given value on the screen - :return: none - """ - denaliMessages.setPressureOcclusionData( - self.sldArterialValue.value(), - self.sldVenousValue.value(), - 0, 0, 0 - ) - - @Slot() - def do_pressures_limits_accept(self): - """ - sends the pressures values message with given value on the screen - :return: none - """ - # vAccepted, vReason, vArterialLow, vArterialHigh, vVenousLow, vVenousHigh - denaliMessages.sendTreatmentAdjustPressuresLimitsResponse( - EResponse.Accepted, 0, - self.spnArterialLimitLow.value(), - self.spnArterialLimitHigh.value(), - self.spnVenousLimitLow.value(), - self.spnVenousLimitHigh.value() - ) - self.lblAVPressuresAction.setText('Accepted ') - - @Slot() - def do_pressures_limits_reject(self): - """ - sends the pressures values message with given value on the screen - :return: none - """ - reason = self.spnAVPressuresRejectReason.value() - denaliMessages.sendTreatmentAdjustPressuresLimitsResponse( - EResponse.Rejected, reason, - self.spnArterialLimitLow.value(), - self.spnArterialLimitHigh.value(), - self.spnVenousLimitLow.value(), - self.spnVenousLimitHigh.value() - ) - self.lblAVPressuresAction.setText('Rejected ' + "{}".format(reason)) - - def initialize(self): - """ - initializes the class by calling it's initializer methods to make objects ready - :return: none - """ - self.setup_ranges() - self.setup_saline_adjustment() - self.setup_saline_data() - self.setup_uf_adjustment() - self.setup_treatment_states() - self.setup_pressures_limits() - - def main(): """ the main function which initializes the Simulator and starts it. @@ -545,17 +33,35 @@ utils.tstStart(__file__) # create qt application - QtWidgets.QApplication(sys.argv) + app = QtWidgets.QApplication(sys.argv) simulator = Simulator() simulator.show() + hd_simulator_instance_counter_check() + utils.tstDone() # start qt application main loop - sys.exit(qApp.exec_()) + sys.exit(app.exec_()) +def hd_simulator_instance_counter_check(): + """ + Checks to make sure only one instance of the HDSimulator has been created. + this code shall be part of the HDSimulator __init__ but other codes are not ready for this. + so only the simulator is checking it now. + """ + if HDSimulator.instanceCount > 1: + raise Exception("more than one instance of HDSimulator shall not be created.") + else: + if HDAlarmsSimulator.instanceCount > 1: + raise Exception("more than one instance of HDAlarmsSimulator shall not be created.") + else: + print("HDAlarmsSimulator number of instances is ", HDAlarmsSimulator.instanceCount) + print("HDSimulator number of instances is ", HDSimulator.instanceCount) + + if __name__ == "__main__": QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts) main()