def set_run(self, run): ''' Sets the run variable directly. Usually called at start up, when reading the run value from the ESP. arguments: - run: the run value (0 or 1) to set ''' if self._run == run: return self._run = run if run == self.DONOT_RUN: # TODO: this should be an alarm msg = MessageBox() msg.critical( 'STOPPING VENTILATION', 'The hardware has stopped the ventilation.', 'The microcontroller has stopped the ventilation by sending run = ' + str(run), 'The microcontroller has stopped the ventilation by sending run = ' + str(run), {msg.Ok: self.show_start_button})() else: self.show_stop_button()
def _raise_comm_error(self, message): """ Opens an error window with 'message'. """ # TODO: find a good exit point msg = MessageBox() msg.critical('COMMUNICATION ERROR', 'Error communicating with the hardware', message, '** COMMUNICATION ERROR **', {msg.Ok: lambda: sys.exit(-1)})()
def test_MessageBox_Critical(qtbot): msg = MessageBox() def foo(): pass callbacks = {msg.Ok: foo} msg.critical("CHANGE OF MODE", "The ventilator changed from PSV to PCV mode.", "The microcontroller raised the backup flag.", "", callbacks, True) assert msg is not None
def _on_click_snooze(self): ''' The callback function called when the alarm snooze button is clicked. ''' if self._mode not in [WARNING, ERROR]: raise Exception('mode must be alarm or warning.') # Reset the alarms/warnings in the ESP # If the ESP connection fails at this # time, raise an error box try: if self._mode == ERROR: self._esp32.snooze_hw_alarm(self._code) self._alarm_h.snooze_alarm(self._code) else: self._esp32.reset_warnings() self._alarm_h.snooze_warning(self._code) except Exception as error: msg = MessageBox() fn = msg.critical("Critical", "Severe hardware communication error", str(error), "Communication error", { msg.Retry: lambda: self.ok_worker(mode), msg.Abort: lambda: None }) fn()
def thread_complete(self): ''' Called when a thread ends. ''' if self._running: print( "\033[91mERROR: The I/O thread finished! Going to start a new one...\033[0m" ) self._n_attempts += 1 self._running = False if self._n_attempts > 10: self._n_attempts = 0 msg = MessageBox() # TODO: find a good exit point callbacks = { msg.Retry: self.start_io_thread, msg.Abort: lambda: sys.exit(-1) } fn = msg.critical("COMMUNICATION ERROR", "CANNOT COMMUNICATE WITH THE HARDWARE", "Check cable connections then click retry.", "COMMUNICATION ERROR", callbacks) fn() time.sleep(0.05) self.start_io_thread()
def ok_worker(self, mode): ''' The callback function called when the alarm or warning pop up window is closed by clicking on the Ok button. arguments: - mode: what this is closing, an 'alarm' or a 'warning' ''' if mode not in ['alarm', 'warning']: raise Exception('mode must be alarm or warning.') if mode == 'alarm': self._alarm_raised = False else: self._warning_raised = False # Reset the alarms/warnings in the ESP # If the ESP connection fails at this # time, raise an error box try: if mode == 'alarm': self._esp32.reset_alarms() else: self._esp32.reset_warnings() except Exception as error: msg = MessageBox() fn = msg.critical("Critical", "Severe hardware communication error", str(error), "Communication error", { msg.Retry: lambda: self.ok_worker(mode), msg.Abort: lambda: None }) fn()
def handle_alarms(self): ''' The callback method which is called periodically to check if the ESP raised any alarm or warning. If an alarm or warning is raised, a pop up window appears, showing the list of alarms and warnings. If more alarms or warnings add up, the window is updated automatically showing the latest errors. ''' # Retrieve alarms and warnings from the ESP try: esp32alarm = self._esp32.get_alarms() esp32warning = self._esp32.get_warnings() except Exception as error: esp32alarm = None esp32warning = None err_msg = "Severe hardware communication error. " err_msg += "Cannot retrieve alarm and warning statuses from hardware." msg = MessageBox() fn = msg.critical("Critical", err_msg, str(error), "Communication error", { msg.Retry: lambda: None, msg.Abort: lambda: None }) fn() # # ALARMS # if esp32alarm: errors = esp32alarm.strerror_all() errors_full = esp32alarm.strerror_all(append_err_no=True) alarm_codes = esp32alarm.get_alarm_codes() for alarm_code, err_str in zip(alarm_codes, errors): if alarm_code not in self._err_buttons: btn = AlarmButton(ERROR, alarm_code, err_str, self._alarmlabel, self._snooze_btn) self._alarmstack.addWidget(btn) self._err_buttons[alarm_code] = btn # # WARNINGS # if esp32warning: errors = esp32warning.strerror_all() errors_full = esp32warning.strerror_all(append_err_no=True) warning_codes = esp32warning.get_alarm_codes() for warning_code, err_str in zip(warning_codes, errors): if warning_code not in self._war_buttons: btn = AlarmButton(WARNING, warning_code, err_str, self._alarmlabel, self._snooze_btn) self._alarmstack.addWidget(btn) self._war_buttons[warning_code] = btn
def set_run(self, run): ''' Sets the run variable directly. Usually called at start up, when reading the run value from the ESP. ''' if self.run == run: return if self.run == self.DO_RUN: msg = MessageBox() msg.critical('STOPPING VENTILATION', 'The hardware has stopped the ventilation.', 'The microcontroller has stopped the ventilation by sending run = '+str(run), 'The microcontroller has stopped the ventilation by sending run = '+str(run), {msg.Ok: self._stop_abruptly})() else: self.toggle_start_stop()
def send_signal(self, mode, pause): ''' Sends signal the appropriate signal the ESP to pause inpiration or expiration. ''' try: if not self._data_h.set_data(mode, int(pause)): raise Exception('Call to set_data failed.') except Exception as error: msg = MessageBox() fn = msg.critical("Critical", "Severe hardware communication error", str(error), "Communication error", {msg.Ok: lambda: self.stop_timer()}) fn()
def open_comm_error(self, error): ''' Opens a message window if there is a communication error. ''' msg = MessageBox() # TODO: find a good exit point callbacks = { msg.Retry: self._restart_timer, msg.Abort: lambda: sys.exit(-1) } fn = msg.critical( "COMMUNICATION ERROR", "CANNOT COMMUNICATE WITH THE HARDWARE", "Check cable connections then click retry.\n" + error, "COMMUNICATION ERROR", callbacks) fn()
def start_gui(): """ Launch the MVM GUI """ base_dir = os.path.dirname(__file__) settings_file = os.path.join(base_dir, 'default_settings.yaml') with open(settings_file) as mvm_settings: config = yaml.load(mvm_settings, Loader=yaml.FullLoader) print('Config:', yaml.dump(config), sep='\n') app = QtWidgets.QApplication(sys.argv) esp32 = None if 'fakeESP32' in sys.argv: print('******* Simulating communication with ESP32') esp32 = FakeESP32Serial(config) else: while True: try: err_msg = "Cannot communicate with port %s" % config['port'] esp32 = ESP32Serial(config) break except SerialException as error: msg = MessageBox() retry = msg.critical("Do you want to retry?", "Severe hardware communication error", str(error) + err_msg, "Communication error", {msg.Retry: lambda: True, msg.Abort: lambda: sys.exit(-1)}) if not retry(): break if esp32 is None: exit(-1) esp32.set("wdenable", 1) watchdog = QtCore.QTimer() watchdog.timeout.connect(esp32.set_watchdog) watchdog.start(config["wdinterval"] * 1000) window = MainWindow(config, esp32) window.show() app.exec_() esp32.set("wdenable", 0)
def send_signal(self, mode, pause): """ Sends signal the appropriate signal the ESP to pause inpiration or expiration. arguments: - mode: The pause mode (either 'pause_exhale' or 'pause_inhale') - pause: Boolean for paused or not paused """ try: if not self._data_h.set_data(mode, int(pause)): raise Exception('Call to set_data failed.') except Exception as error: msg = MessageBox() confirm_func = msg.critical( "Critical", "Severe hardware communication error", str(error), "Communication error", {msg.Ok: lambda: self.stop_timer(mode)}) confirm_func()
def connect_esp32(config): try: if 'fakeESP32' in sys.argv: print('******* Simulating communication with ESP32') err_msg = "Cannot setup FakeESP32Serial" esp32 = FakeESP32Serial(config) esp32.set("wdenable", 1) else: err_msg = "Cannot communicate with port %s" % config['port'] esp32 = ESP32Serial(config) esp32.set("wdenable", 1) except Exception as error: msg = MessageBox() fn = msg.critical("Do you want to retry?", "Severe hardware communication error", str(error) + err_msg, "Communication error", { msg.Retry: lambda: connect_esp32(config), msg.Abort: lambda: None}) return fn() return esp32
class AlarmHandler: ''' This class starts a QTimer dedicated to checking is there are any errors or warnings coming from ESP32 ''' def __init__(self, config, esp32): ''' Constructor arguments: - config: the dictionary storing the configuration - esp32: the esp32serial object ''' self._config = config self._esp32 = esp32 self._alarm_raised = False self._warning_raised = False self._msg_err = MessageBox() self._msg_war = MessageBox() self._alarm_timer = QtCore.QTimer() self._alarm_timer.timeout.connect(self.handle_alarms) self._alarm_timer.start(config["alarminterval"] * 1000) def handle_alarms(self): ''' The callback method which is called periodically to check if the ESP raised any alarm or warning. If an alarm or warning is raised, a pop up window appears, showing the list of alarms and warnings. If more alarms or warnings add up, the window is updated automatically showing the latest errors. ''' # Retrieve alarms and warnings from the ESP try: esp32alarm = self._esp32.get_alarms() esp32warning = self._esp32.get_warnings() except Exception as error: esp32alarm = None esp32warning = None err_msg = "Severe hardware communication error. " err_msg += "Cannot retrieve alarm and warning statuses from hardware." msg = MessageBox() fn = msg.critical("Critical", err_msg, str(error), "Communication error", { msg.Retry: lambda: None, msg.Abort: lambda: None }) fn() # # ALARMS # if esp32alarm: errors = esp32alarm.strerror_all() errors_full = esp32alarm.strerror_all(append_err_no=True) if not self._alarm_raised: self._alarm_raised = True self._msg_err.critical("ALARM", " - ".join(errors), "\n".join(errors_full), "Alarm received.", { self._msg_err.Ignore: lambda: self.ok_worker('alarm', esp32alarm) }, do_not_block=True) self._msg_err.move(0, 100) self._msg_err.open() else: # If the window is already opened, just change the text self._msg_err.setInformativeText(" - ".join(errors)) self._msg_err.setDetailedText("\n".join(errors_full)) self._msg_err.raise_() # # WARNINGS # if esp32warning: errors = esp32warning.strerror_all() errors_full = esp32warning.strerror_all(append_err_no=True) if not self._warning_raised: self._warning_raised = True self._msg_war.warning("WARNING", " - ".join(errors), "\n".join(errors_full), "Warning received.", { self._msg_war.Ok: lambda: self.ok_worker('warning', esp32warning) }, do_not_block=True) self._msg_war.move(0, 300) self._msg_war.open() else: # If the window is already opened, just change the text self._msg_war.setInformativeText(" - ".join(errors)) self._msg_war.setDetailedText("\n".join(errors_full)) self._msg_war.raise_() def ok_worker(self, mode, raised_ones): ''' The callback function called when the alarm or warning pop up window is closed by clicking on the Ok button. arguments: - mode: what this is closing, an 'alarm' or a 'warning' ''' if mode not in ['alarm', 'warning']: raise Exception('mode must be alarm or warning.') if mode == 'alarm': self._alarm_raised = False else: self._warning_raised = False # Reset the alarms/warnings in the ESP # If the ESP connection fails at this # time, raise an error box try: if mode == 'alarm': for alarm_code in raised_ones.unpack(): self._esp32.snooze_hw_alarm(alarm_code) else: self._esp32.reset_warnings() except Exception as error: msg = MessageBox() fn = msg.critical("Critical", "Severe hardware communication error", str(error), "Communication error", { msg.Retry: lambda: self.ok_worker(mode), msg.Abort: lambda: None }) fn() def raise_alarm(self): ''' Raises an alarm in the ESP ''' self._esp32.raise_gui_alarm() def stop_alarm(self, code): ''' Stops an alarm in the ESP ''' self._esp32.reset_alarms()
def handle_alarms(self): ''' The callback method which is called periodically to check if the ESP raised any alarm or warning. If an alarm or warning is raised, a pop up window appears, showing the list of alarms and warnings. If more alarms or warnings add up, the window is updated automatically showing the latest errors. ''' # Retrieve alarms and warnings from the ESP try: esp32alarm = self._esp32.get_alarms() esp32warning = self._esp32.get_warnings() except Exception as error: esp32alarm = None esp32warning = None err_msg = "Severe hardware communication error. " err_msg += "Cannot retrieve alarm and warning statuses from hardware." msg = MessageBox() fn = msg.critical("Critical", err_msg, str(error), "Communication error", { msg.Retry: lambda: None, msg.Abort: lambda: None }) fn() # # ALARMS # if esp32alarm: errors = esp32alarm.strerror_all() errors_full = esp32alarm.strerror_all(append_err_no=True) if not self._alarm_raised: self._alarm_raised = True self._msg_err.critical("ALARM", " - ".join(errors), "\n".join(errors_full), "Alarm received.", { self._msg_err.Ignore: lambda: self.ok_worker('alarm', esp32alarm) }, do_not_block=True) self._msg_err.move(0, 100) self._msg_err.open() else: # If the window is already opened, just change the text self._msg_err.setInformativeText(" - ".join(errors)) self._msg_err.setDetailedText("\n".join(errors_full)) self._msg_err.raise_() # # WARNINGS # if esp32warning: errors = esp32warning.strerror_all() errors_full = esp32warning.strerror_all(append_err_no=True) if not self._warning_raised: self._warning_raised = True self._msg_war.warning("WARNING", " - ".join(errors), "\n".join(errors_full), "Warning received.", { self._msg_war.Ok: lambda: self.ok_worker('warning', esp32warning) }, do_not_block=True) self._msg_war.move(0, 300) self._msg_war.open() else: # If the window is already opened, just change the text self._msg_war.setInformativeText(" - ".join(errors)) self._msg_war.setDetailedText("\n".join(errors_full)) self._msg_war.raise_()
def send_values_to_hardware(self): ''' Sends the currently set values to the ESP ''' settings_to_file = {} for param, btn in self._all_spinboxes.items(): settings_to_file[param] = self._current_values[param] # value is the variable to be sent to the hardware, # so possibly converted from the settings if param in ['enable_backup', 'pcv_trigger_enable']: value = int(self._current_values[param]) elif param == 'insp_expir_ratio': i_over_e = 1. / self._current_values[param] value = 1. / (i_over_e + 1) else: value = self._current_values[param] if 'conversion' in self._config[param]: value = value * self._config[param]['conversion'] if self._debug: print('Converting value for', param, 'from', value / self._config[param].get('conversion', 1.), 'to', value) if self._debug: print('Setting value of', param, ':', value) # Update the value in the config file self._config[param]['current'] = self._current_values[param] # Set color to red until we know the value has been set. btn.setStyleSheet("color: red") esp_param_name = self._config['esp_settable_param'][param] # Finally, try to set the value to the ESP # Raise an error message if this fails. try: if self._data_h.set_data(esp_param_name, value): # Now set the color to green, as we know it has been set btn.setStyleSheet("color: green") except Exception as error: msg = MessageBox() msg.critical( "Critical", "Severe Hardware Communication Error", str(error), "Communication error", { msg.Retry: lambda: self.send_values_to_hardware, msg.Abort: lambda: sys.exit(-1) })() if param == 'respiratory_rate': self.toolsettings_lookup["respiratory_rate"].update(value) elif param == 'insp_expir_ratio': self.toolsettings_lookup["insp_expir_ratio"].update( self._current_values[param]) elif param == 'insp_pressure': self.toolsettings_lookup["insp_pressure"].update(value) settings_file = SettingsFile(self._config["settings_file_path"]) settings_file.store(settings_to_file)