Commit 20205701 authored by Benjamin Mummery's avatar Benjamin Mummery 💻

Merge branch 'ui_dev' into feature/ui_localisation

parents b842ef31 de2eb63b
......@@ -19,6 +19,7 @@ env/
ansible/playbooks/hosts
.idea
scratch_*
NativeUI/configs/startup_config.json
# python virtual env created using ansible
.hev_env
......
This diff is collapsed.
#!/usr/bin/env python3
"""
tab_alarms.py
"""
__author__ = ["Benjamin Mummery", "Tiago Sarmento"]
__credits__ = ["Benjamin Mummery", "Dónal Murray", "Tim Powell", "Tiago Sarmento"]
__license__ = "GPL"
__version__ = "0.0.1"
__maintainer__ = "Tiago Sarmento"
__email__ = "tiago.sarmento@stfc.ac.uk"
__status__ = "Prototype"
import sys
from datetime import datetime
from PySide2 import QtCore, QtGui, QtWidgets
from handler_library.handler import PayloadHandler
import logging
class AlarmHandler(PayloadHandler):
UpdateAlarm = QtCore.Signal(dict)
NewAlarm = QtCore.Signal(QtWidgets.QWidget)
RemoveAlarm = QtCore.Signal(QtWidgets.QWidget)
def __init__(self, NativeUI, *args, **kwargs):
super().__init__(['DATA', 'ALARM'],*args, **kwargs)
self.NativeUI = NativeUI
self.alarmDict = {}
self.alarm_list = []
self.oldAlarms = []
def acknowledge_pressed(self):
self.popup.clearAlarms()
self.list.acknowledge_all()
def _set_alarm_list(self, alarm_list:list):
self.alarm_list = alarm_list
def active_payload(self, *args) -> int:
#alarm_data = self.get_db()
#outdict = {}
full_payload = args[0]
#print(full_payload['alarms'])
currentAlarms = full_payload['alarms']#self.NativeUI.ongoingAlarms # instead of getting database at a particular frequency, this should be triggered when a new alarm arrives
self.alarm_list = currentAlarms
#self._set__alarm_list(currentAlarms)
if self.oldAlarms != currentAlarms:
if len(self.oldAlarms) != len(currentAlarms):
self.oldAlarms = currentAlarms
self.UpdateAlarm.emit(currentAlarms)
def handle_newAlarm(self, currentAlarms): # if this is combined with active_payload an error arises
for alarm in currentAlarms:
alarmCode = alarm["alarm_code"]
if alarmCode in self.alarmDict:
self.alarmDict[alarmCode].resetTimer()
self.alarmDict[alarmCode].calculateDuration()
else:
newAbstractAlarm = AbstractAlarm(self.NativeUI, alarm)
self.alarmDict[alarmCode] = newAbstractAlarm
self.NewAlarm.emit(newAbstractAlarm)
newAbstractAlarm.alarmExpired.connect(
lambda i=newAbstractAlarm: self.handleAlarmExpiry(i)
)
def handleAlarmExpiry(self, abstractAlarm):
abstractAlarm.freezeTimer()
abstractAlarm.recordFinishTime()
self.RemoveAlarm.emit(abstractAlarm)
self.alarmDict.pop(abstractAlarm.alarmPayload["alarm_code"])
# abstractAlarm is deleted by itself
class AbstractAlarm(QtCore.QObject):
alarmExpired = QtCore.Signal()
def __init__(self, NativeUI, alarmPayload, *args, **kwargs):
super(AbstractAlarm, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
self.alarmPayload = alarmPayload
self.startTime = datetime.now()
self.duration = datetime.now() - self.startTime
self.finishTime = -1
self.timer = QtCore.QTimer()
self.timer.setInterval(2000) # just faster than 60Hz
self.timer.timeout.connect(self.timeoutDelete)
self.timer.start()
def timeoutDelete(self):
# """Check alarm still exists in ongoingAlarms object. If present do nothing, otherwise delete."""
self.alarmExpired.emit()
self.setParent(None) # delete self
return 0
def resetTimer(self):
self.timer.start()
return 0
def freezeTimer(self):
self.timer.stop()
return 0
def recordFinishTime(self):
self.finishTime = datetime.now()
self.duration = self.finishTime - self.startTime
def calculateDuration(self):
self.duration = datetime.now() - self.startTime
......@@ -18,17 +18,16 @@ from PySide2 import QtCore, QtGui, QtWidgets
from datetime import datetime
class alarmList(QtWidgets.QListWidget):
class AlarmList(QtWidgets.QListWidget):
def __init__(self, NativeUI, *args, **kwargs):
super(alarmList, self).__init__(*args, **kwargs)
super(AlarmList, self).__init__(*args, **kwargs)
self.labelList = []
self.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)
self.setStyleSheet(
"background-color:white;" "font-size: " + NativeUI.text_size + ";"
)
self.setStyleSheet("background-color:white;")
self.setFont(NativeUI.text_font)
iconpath_bell = os.path.join(NativeUI.iconpath, "bell-solid.png")
iconpath_bellReg = os.path.join(NativeUI.iconpath, "bell-regular.png")
......@@ -36,10 +35,9 @@ class alarmList(QtWidgets.QListWidget):
self.solidBell = QtGui.QIcon(iconpath_bell)
self.regularBell = QtGui.QIcon(iconpath_bellReg)
newItem = QtWidgets.QListWidgetItem(' ')
newItem = QtWidgets.QListWidgetItem(" ")
self.addItem(newItem)
def acknowledge_all(self):
for x in range(self.count() - 1):
self.item(x).setText("acknowledgedAlarm")
......@@ -47,9 +45,16 @@ class alarmList(QtWidgets.QListWidget):
def addAlarm(self, abstractAlarm):
timestamp = str(abstractAlarm.startTime)[:-3]
newItem = QtWidgets.QListWidgetItem(self.solidBell, timestamp + ': ' + abstractAlarm.alarmPayload['alarm_type'] + ' - ' + abstractAlarm.alarmPayload["alarm_code"])
newItem = QtWidgets.QListWidgetItem(
self.solidBell,
timestamp
+ ": "
+ abstractAlarm.alarmPayload["alarm_type"]
+ " - "
+ abstractAlarm.alarmPayload["alarm_code"],
)
self.insertItem(0, newItem) # add to the top
#self.labelList
# self.labelList
def removeAlarm(self, abstractAlarm):
for x in range(self.count() - 1):
......@@ -57,7 +62,6 @@ class alarmList(QtWidgets.QListWidget):
self.takeItem(x)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widg = alarmList()
......
......@@ -17,55 +17,16 @@ from PySide2 import QtCore, QtGui, QtWidgets
from datetime import datetime
class abstractAlarm(QtWidgets.QWidget):
alarmExpired = QtCore.Signal()
def __init__(self, NativeUI, alarmPayload, *args, **kwargs):
super(abstractAlarm, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
self.alarmPayload = alarmPayload
self.startTime = datetime.now()
self.duration = datetime.now() - self.startTime
self.finishTime = -1
self.timer = QtCore.QTimer()
self.timer.setInterval(1500) # just faster than 60Hz
self.timer.timeout.connect(self.timeoutDelete)
self.timer.start()
def timeoutDelete(self):
# """Check alarm still exists in ongoingAlarms object. If present do nothing, otherwise delete."""
self.alarmExpired.emit()
self.setParent(None) # delete self
return 0
def resetTimer(self):
self.timer.start()
return 0
def freezeTimer(self):
self.timer.stop()
return 0
def recordFinishTime(self):
self.finishTime = datetime.now()
self.duration = self.finishTime - self.startTime
def calculateDuration(self):
self.duration = datetime.now() - self.startTime
class alarmWidget(QtWidgets.QWidget):
class AlarmWidget(QtWidgets.QWidget):
"""Object containing information particular to one alarm.
Created when alarm received from microcontroller, timeout after alarm signal stops.
Is contained within alarmPopup"""
def __init__(self, NativeUI, abstractAlarm, alarmCarrier, *args, **kwargs):
super(alarmWidget, self).__init__(*args, **kwargs)
super(AlarmWidget, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
self.alarmCarrier = alarmCarrier # Needs to refer to its containing object
self.alarmCarrier = alarmCarrier # Needs to refer to its containing object
self.layout = QtWidgets.QHBoxLayout()
self.layout.setSpacing(0)
......@@ -81,10 +42,14 @@ class alarmWidget(QtWidgets.QWidget):
self.layout.addWidget(iconLabel)
self.textLabel = QtWidgets.QLabel()
self.textLabel.setText(self.alarmPayload['alarm_type']+ ' - ' + self.alarmPayload["alarm_code"])
alarmLevel = self.alarmPayload["alarm_type"].replace('PRIORITY_', '')
self.textLabel.setText(
alarmLevel + " - " + self.alarmPayload["alarm_code"]
)
self.textLabel.setFixedWidth(400)
self.textLabel.setAlignment(QtCore.Qt.AlignCenter)
self.textLabel.setStyleSheet("font-size: " + NativeUI.text_size + ";")
self.textLabel.setFont(NativeUI.text_font)
#self.textLabel.setStyleSheet("font-size: " + NativeUI.text_size + ";")
self.layout.addWidget(self.textLabel)
self.setFixedHeight(40)
......@@ -106,6 +71,17 @@ class alarmWidget(QtWidgets.QWidget):
self.NativeUI.alarms_view.alarmButton.click()
return False
def get_priority(self):
return self.alarmPayload["alarm_type"]
def setFont(self, font) -> int:
"""
Set the font for textLabel.
"""
self.textLabel.setFont(font)
return 0
# def checkAlarm(self):
# """Check alarm still exists in ongoingAlarms object. If present do nothing, otherwise delete."""
# self.ongoingAlarms = self.NativeUI.ongoingAlarms
......@@ -117,14 +93,15 @@ class alarmWidget(QtWidgets.QWidget):
# return 0
class alarmPopup(QtWidgets.QDialog):
class AlarmPopup(QtWidgets.QDialog):
"""Container class for alarm widgets. Handles ordering and positioning of alarms.
Needs to adjust its size whenever a widget is deleted"""
def __init__(self, NativeUI, *args, **kwargs):
super(alarmPopup, self).__init__(*args, **kwargs)
super(AlarmPopup, self).__init__(*args, **kwargs)
self.setParent(NativeUI) # ensures popup closes when main UI does
self.alarmDict = {}
self.NativeUI = NativeUI
self.extraAlarms = AlarmExtrasWidget(NativeUI,self)
self.layout = QtWidgets.QVBoxLayout()
self.layout.setSpacing(0)
......@@ -133,7 +110,9 @@ class alarmPopup(QtWidgets.QDialog):
self.location_on_window()
self.setWindowFlags(
QtCore.Qt.FramelessWindowHint | QtCore.Qt.Dialog | QtCore.Qt.WindowStaysOnTopHint
QtCore.Qt.FramelessWindowHint
| QtCore.Qt.Dialog
| QtCore.Qt.WindowStaysOnTopHint
) # no window title
self.shadow = QtWidgets.QGraphicsDropShadowEffect()
......@@ -146,6 +125,8 @@ class alarmPopup(QtWidgets.QDialog):
self.timer.timeout.connect(self.adjustSize)
self.timer.start()
self.show()
def clearAlarms(self):
"""Wipe all alarms out and clear dictionary"""
for i in reversed(range(self.layout.count())):
......@@ -157,19 +138,45 @@ class alarmPopup(QtWidgets.QDialog):
def addAlarm(self, abstractAlarm):
"""Creates a new alarmWidget and adds it to the container"""
self.alarmDict[abstractAlarm.alarmPayload["alarm_code"]] = alarmWidget(
self.alarmDict[abstractAlarm.alarmPayload["alarm_code"]] = AlarmWidget(
self.NativeUI, abstractAlarm, self
)
self.layout.addWidget(self.alarmDict[abstractAlarm.alarmPayload["alarm_code"]])
self.refresh_alarm_ordering()
#self.layout.addWidget(self.alarmDict[abstractAlarm.alarmPayload["alarm_code"]])
return 0
def removeAlarm(self, abstractAlarm):
"""Creates a new alarmWidget and adds it to the container"""
self.alarmDict[abstractAlarm.alarmPayload["alarm_code"]].setParent(None)
self.alarmDict.pop(abstractAlarm.alarmPayload["alarm_code"])
self.refresh_alarm_ordering()
return 0
def refresh_alarm_ordering(self):
self.layout.removeWidget(self.extraAlarms)
for key in self.alarmDict:
self.layout.removeWidget(self.alarmDict[key])
for key in self.alarmDict:
if self.alarmDict[key].get_priority() == 'PRIORITY_HIGH':
if self.layout.count() == 4:
self.extraAlarms.update_text(1 + len(self.alarmDict) - self.layout.count())
self.layout.addWidget(self.extraAlarms)
break
self.layout.addWidget(self.alarmDict[key])
if self.layout.count() < 5:
for key in self.alarmDict:
if self.layout.count() == 3:
self.extraAlarms.update_text(len(self.alarmDict) - self.layout.count())
self.layout.addWidget(self.extraAlarms)
break
if self.alarmDict[key].get_priority() == 'PRIORITY_LOW':
self.layout.addWidget(self.alarmDict[key])
# def resetTimer(self, alarmPayload):
# self.alarmDict[alarmPayload["alarm_code"]].timer.start()
......@@ -180,3 +187,60 @@ class alarmPopup(QtWidgets.QDialog):
y = 0 # screen.height() - widget.height()
self.move(x, y)
return 0
class AlarmExtrasWidget(QtWidgets.QWidget):
"""Object containing information particular to one alarm.
Created when alarm received from microcontroller, timeout after alarm signal stops.
Is contained within alarmPopup"""
def __init__(self, NativeUI, alarmCarrier, *args, **kwargs):
super(AlarmExtrasWidget, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
self.alarmCarrier = alarmCarrier # Needs to refer to its containing object
self.layout = QtWidgets.QHBoxLayout()
self.layout.setSpacing(0)
self.layout.setMargin(0)
#self.alarmPayload = abstractAlarm.alarmPayload
iconLabel = QtWidgets.QLabel()
iconpath_check = os.path.join(
self.NativeUI.iconpath, "exclamation-triangle-solid.png"
)
pixmap = QtGui.QPixmap(iconpath_check).scaledToHeight(40)
iconLabel.setPixmap(pixmap)
self.layout.addWidget(iconLabel)
self.textLabel = QtWidgets.QLabel()
self.textLabel.setText('1 More Alarms')
self.textLabel.setFixedWidth(400)
self.textLabel.setAlignment(QtCore.Qt.AlignCenter)
self.textLabel.setFont(NativeUI.text_font)
#self.textLabel.setStyleSheet("font-size: " + NativeUI.text_size + ";")
self.layout.addWidget(self.textLabel)
self.setFixedHeight(40)
self.setLayout(self.layout)
self.setStyleSheet("background-color:red;")
#self.priority = "PRIORITY_LOW"
def update_text(self, num):
self.textLabel.setText(str(num)+ ' More Alarms')
#self.code =
# self.timer = QtCore.QTimer()
# self.timer.setInterval(500) # just faster than 60Hz
# self.timer.timeout.connect(self.checkAlarm)
# self.timer.start()
# self.installEventFilter(self)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.MouseButtonPress):
self.NativeUI.leftBar.tab_page_buttons.button_alarms.click()
self.NativeUI.alarms_view.alarmButton.click()
return False
def get_priority(self):
return self.alarmPayload["alarm_type"]
......@@ -18,42 +18,49 @@ from PySide2 import QtCore, QtGui, QtWidgets
from datetime import datetime
class alarmTable(QtWidgets.QTableWidget):
class AlarmTable(QtWidgets.QTableWidget):
def __init__(self, NativeUI, *args, **kwargs):
super(alarmTable, self).__init__(*args, **kwargs)
super(AlarmTable, self).__init__(*args, **kwargs)
self.alarmDict = {}
self.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)
self.setStyleSheet(
"background-color:white;" "font-size: " + NativeUI.text_size + ";"
)
self.setStyleSheet("background-color:white;")
self.setFont(NativeUI.text_font)
self.nrows = 0
self.setColumnCount(4)
self.setSortingEnabled(True)
if self.nrows == 0:
self.setHorizontalHeaderLabels(['Timestamp', 'Priority Level', 'Alarm Code', 'Duration'])
self.setHorizontalHeaderLabels(
["Timestamp", "Priority Level", "Alarm Code", "Duration"]
)
self.payloadKeys = ['alarm_type', 'alarm_code']
self.payloadKeys = ["alarm_type", "alarm_code"]
self.resizeColumnsToContents()
self.alarmDict = {}
self.timer = QtCore.QTimer()
self.timer.setInterval(100)
#self.timer.timeout.connect(self.updateDuration)
# self.timer.timeout.connect(self.updateDuration)
self.timer.start()
def addAlarm(self, abstractAlarm):
timestamp = str(datetime.now())[:-3]
newItem = QtWidgets.QListWidgetItem(self.solidBell, timestamp + ': ' + abstractAlarm.alarmPayload['alarm_type'] + ' - ' + abstractAlarm.alarmPayload["alarm_code"])
newItem = QtWidgets.QListWidgetItem(
self.solidBell,
timestamp
+ ": "
+ abstractAlarm.alarmPayload["alarm_type"]
+ " - "
+ abstractAlarm.alarmPayload["alarm_code"],
)
self.insertItem(0, newItem) # add to the top
#self.labelList
# self.labelList
#widg = self.cellWidget(rowNumber, 4)
#cellItem.setText(str(abstractAlarm.duration))
# widg = self.cellWidget(rowNumber, 4)
# cellItem.setText(str(abstractAlarm.duration))
# abstractAlarm.alarmExpired.connect(lambda i =newItem, j = abstractAlarm: self.update_duration(i,j))
# self.setItem(self.nrows, colnum, newItem)
# tableItem.setText(str(abstractAlarm.duration))
......@@ -65,24 +72,28 @@ class alarmTable(QtWidgets.QTableWidget):
def addAlarmRow(self, abstractAlarm):
self.setSortingEnabled(False)
self.setRowCount(self.nrows+1)
self.setRowCount(self.nrows + 1)
colnum = 0
newItem = QtWidgets.QTableWidgetItem(str(abstractAlarm.startTime)[:-3])
self.setItem(self.nrows, 0, newItem)
newItem = QtWidgets.QTableWidgetItem(abstractAlarm.alarmPayload['alarm_type'])
newItem = QtWidgets.QTableWidgetItem(abstractAlarm.alarmPayload["alarm_type"])
self.setItem(self.nrows, 1, newItem)
newItem = QtWidgets.QTableWidgetItem(abstractAlarm.alarmPayload['alarm_code'])
newItem = QtWidgets.QTableWidgetItem(abstractAlarm.alarmPayload["alarm_code"])
self.setItem(self.nrows, 2, newItem)
newItem = QtWidgets.QTableWidgetItem(' ')
newItem = QtWidgets.QTableWidgetItem(" ")
self.alarmDict[self.nrows] = newItem
self.setItem(self.nrows, 3, self.alarmDict[self.nrows])
#abstractAlarm.alarmExpired.connect(lambda i = self.alarmDict[self.nrows], j = abstractAlarm: self.update_duration(i,j))
self.timer.timeout.connect(lambda i = self.alarmDict[self.nrows], j = abstractAlarm: self.update_duration(i,j))
# abstractAlarm.alarmExpired.connect(lambda i = self.alarmDict[self.nrows], j = abstractAlarm: self.update_duration(i,j))
self.timer.timeout.connect(
lambda i=self.alarmDict[self.nrows], j=abstractAlarm: self.update_duration(
i, j
)
)
if self.nrows == 1:
self.resizeColumnsToContents()
self.nrows = self.nrows + 1
......@@ -90,8 +101,6 @@ class alarmTable(QtWidgets.QTableWidget):
def update_duration(self, cellItem, abstractAlarm):
cellItem.setText(str(abstractAlarm.duration))
if __name__ == "__main__":
......
......@@ -16,16 +16,12 @@ import sys
from alarm_widgets.alarm_popup import alarmPopup, abstractAlarm
from alarm_widgets.alarm_list import alarmList
from PySide2 import QtWidgets
from PySide2 import QtCore, QtGui, QtWidgets
class TabAlarm(QtWidgets.QWidget):
"""
Placeholder
"""
def __init__(self, NativeUI, *args, **kwargs):
super().__init__(*args, **kwargs)
super(TabAlarm, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
# self.alarmDict = {}
......@@ -49,21 +45,14 @@ class TabAlarm(QtWidgets.QWidget):
# self.timer.timeout.connect(self.updateAlarms)
# self.timer.start()
def acknowledge_pressed(self) -> int:
"""
Placeholder
"""
def acknowledge_pressed(self):
self.popup.clearAlarms()
self.list.acknowledge_all()
return 0
def update_alarms(self):
"""
Placeholder
"""
newAlarmPayload = self.NativeUI.get_db("alarms")
if newAlarmPayload == {}:
return 1
return
if newAlarmPayload["alarm_code"] in self.alarmDict:
a = 1
self.alarmDict[newAlarmPayload["alarm_code"]].resetTimer()
......@@ -72,20 +61,15 @@ class TabAlarm(QtWidgets.QWidget):
newAbstractAlarm = abstractAlarm(self.NativeUI, newAlarmPayload)
self.alarmDict[newAlarmPayload["alarm_code"]] = newAbstractAlarm
newAbstractAlarm.alarmExpired.connect(
lambda i=newAbstractAlarm: self.handle_alarm_expiry(i)
lambda i=newAbstractAlarm: self.handleAlarmExpiry(i)
)
self.popup.addAlarm(newAbstractAlarm)
self.list.addAlarm(newAbstractAlarm)
self.NativeUI.widgets.alarm_table_tab.table.addAlarmRow(newAbstractAlarm)
return 0
def handle_alarm_expiry(self, abstract_alarm):
"""
Placeholder
"""
abstract_alarm.freezeTimer()
self.popup.removeAlarm(abstract_alarm)
self.list.removeAlarm(abstract_alarm)
self.alarmDict.pop(abstract_alarm.alarmPayload["alarm_code"])
abstract_alarm.recordFinishTime()
return 0
def handleAlarmExpiry(self, abstractAlarm):
abstractAlarm.freezeTimer()
self.popup.removeAlarm(abstractAlarm)
self.list.removeAlarm(abstractAlarm)
self.alarmDict.pop(abstractAlarm.alarmPayload["alarm_code"])
abstractAlarm.recordFinishTime()
{
"settings":[
[["APNEA", "ms", "APNEA", "SET_THRESHOLD_MIN", "APNEA", 5, 20, 10, 1, 0]],
[["Check Pressure Patient", "ms", "CHECK_P_PATIENT", "SET_THRESHOLD_MIN", "CHECK_P_PATIENT"],["Check Pressure Patient", "ms", "CHECK_P_PATIENT", "SET_THRESHOLD_MAX", "CHECK_P_PATIENT"]],
[["FIO2", "%", "HIGH_FIO2", "SET_THRESHOLD_MIN", "HIGH_FIO2", -10, 0, -5, 0.1, 1],["Percentage O2", "", "fiO2_percent", "SET_TARGET_CURRENT", "FIO2_PERCENT", 20, 100, 21, 1, 0],["FIO2", "%", "HIGH_FIO2", "SET_THRESHOLD_MAX", "HIGH_FIO2", 0, 10, 5, 0.1, 1]],
[["Pressure", " ", "HIGH_PRESSURE", "SET_THRESHOLD_MIN", "HIGH_PRESSURE"],["Inhale Pressure","","inspiratory_pressure","SET_TARGET_CURRENT","INSPIRATORY_PRESSURE", 10, 50, 17, 1, 0],["Pressure", " ", "HIGH_PRESSURE", "SET_THRESHOLD_MAX", "HIGH_PRESSURE"],["Pressure", " ", "HIGH_PRESSURE", "SET_THRESHOLD_MAX", "HIGH_PRESSURE"]],
[["Respiratory Rate", " ", "HIGH_RR", "SET_THRESHOLD_MIN", "HIGH_RR", -10, 0, -5, 0.1, 1],["Respiratory Rate","/min","respiratory_rate","SET_TARGET_CURRENT","RESPIRATORY_RATE", 10, 20, 15, 0.1, 1],["Respiratory Rate", " ", "HIGH_RR", "SET_THRESHOLD_MAX", "HIGH_RR", 0, 10, 5, 0.1, 1]],
[["VTE", " ", "HIGH_VTE", "SET_THRESHOLD_MIN", "HIGH_VTE", -10, 0, -5, 1, 0],["Inhale Volume", "", "volume", "SET_TARGET_CURRENT", "VOLUME", 200, 800, 400, 20, 0],["VTE", " ", "HIGH_VTE", "SET_THRESHOLD_MAX", "HIGH_VTE",0, 10, 5, 1, 0]],
[["VTI", " ", "HIGH_VTI", "SET_THRESHOLD_MIN", "HIGH_VTI", -10, 0, -5, 1, 0],["VTI", " ", "HIGH_VTI", "SET_THRESHOLD_MAX", "HIGH_VTI",0, 10, 5, 1, 0]],
[["Occlusion", " ", "OCCLUSION","SET_THRESHOLD_MIN", "OCCLUSION", 5, 20, 15, 1, 0]],
[["PEEP", " ", "HIGH_PEEP","SET_THRESHOLD_MIN", "HIGH_PEEP", -2, 0, -2, 1, 0],["PEEP","cm h2o","peep","SET_TARGET_CURRENT","PEEP", 0, 100, 15, 0.1, 1],["PEEP", " ", "HIGH_PEEP","SET_THRESHOLD_MAX", "HIGH_PEEP",0, 2, 2, 1, 0]]
],
"SingleThresholds": ["APNEA", "Occlusion"],
"AbsoluteLimits": ["Percentage O2", "FIO2", "PEEP"]
}
\ No newline at end of file
{
"settings":[
["APNEA", "ms", "APNEA", "SET_THRESHOLD_MIN", "APNEA"],
["Check Pressure Patient", "ms", "CHECK_P_PATIENT", "SET_THRESHOLD_MIN", "CHECK_P_PATIENT"],
["FIO2", "ms", "HIGH_FIO2", "SET_THRESHOLD_MIN", "HIGH_FIO2"],
["High Pressure", " ", "HIGH_PRESSURE", "SET_THRESHOLD_MIN", "HIGH_PRESSURE"],
["High Respiratory Rate", " ", "HIGH_RR", "SET_THRESHOLD_MIN", "HIGH_RR"],
["High VTE", " ", "HIGH_VTE", "SET_THRESHOLD_MAX", "HIGH_VTE"],
["Low VTE", " ", "LOW_VTE", "SET_THRESHOLD_MIN", "LOW_VTE"],
["High VTI", " ", "HIGH_VTI", "SET_THRESHOLD_MAX", "HIGH_VTI"],
["Low VTI", " ", "LOW_VTI", "SET_THRESHOLD_MIN", "LOW_VTI"],
["Low FIO2", " ", "LOW_FIO2", "SET_THRESHOLD_MIN", "LOW_FIO2"],
["Occlusion", " ", "OCCLUSION","SET_THRESHOLD_MIN", "OCCLUSION"],
["PEEP", " ", "HIGH_PEEP","SET_THRESHOLD_MAX", "HIGH_PEEP"],
["Low PEEP", " ", "LOW_PEEP","SET_THRESHOLD_MIN", "LOW_PEEP"] ],
"HighLowLimits": ["High Pressure", "Occlusion"]
}
\ No newline at end of file
......@@ -9,6 +9,8 @@
"label_foreground":[200, 200, 200],
"display_background":[200, 200, 200],
"display_foreground":[0, 0, 0],
"display_foreground_changed":[0, 200, 0],
"display_foreground_red":[200, 0, 0],
"baby_blue":[144, 231, 211],
"red":[200, 0, 0],
"green":[0, 200, 0],
......
{
"Buffers": [
[
"Calibration",
"ms",
"duration_calibration",
"SET_DURATION",
"CALIBRATION",
0,
1000,
50,
0
],
["Purge", "ms", "duration_buff_purge", "SET_DURATION", "BUFF_PURGE"],
["Flush", "ms", "duration_buff_flush", "SET_DURATION", "BUFF_FLUSH"],
[
"Pre-fill",
"ms",
"duration_buff_prefill",
"SET_DURATION",
"BUFF_PREFILL"
],
["Fill", "ms", "duration_buff_prefill", "SET_DURATION", "BUFF_FILL"],
[
"Pre-inhale",
"ms",
"duration_buff_pre_inhale",
"SET_DURATION",
"BUFF_PRE_INHALE"
]
],
"PID": [
["KP", "", "kp", "SET_PID", "KP"],
["KI", "", "ki", "SET_PID", "KI"],
["KD", "", "kd", "SET_PID", "KD"],
["PID Gain", "", "pid_gain", "SET_PID", "PID_GAIN"],
[
"Max. PP",
"",
"max_patient_pressure",
"SET_PID",
"MAX_PATIENT_PRESSURE"
]
],
"Valves": [
["Air in", "", "valve_air_in"],
["O2 in", "", "valve_o2_in"],
["Inhale", "", "valve_inhale"],
["Exhale", "", "valve_exhale"],
["Purge valve", "", "valve_purge"],
["Inhale Opening", "%", "valve_inhale_percent"],
["Exhale Opening", "%", "valve_exhale_percent"]
],
"Breathing": [
["Inhale", "ms", "duration_inhale", "SET_DURATION", "INHALE"],
["Pause", "ms", "duration_pause", "SET_DURATION", "PAUSE"],
["Exhale fill", "ms", "duration_exhale", "SET_DURATION", "EXHALE_FILL"],
["Exhale", "ms", "duration_exhale", "SET_DURATION", "EXHALE"],
["I:E Ratio", "", "inhale_exhale_ratio"]
]
}
\ No newline at end of file
{"settings":[
["Respiratory Rate","/min","respiratory_rate","SET_TARGET_","RESPIRATORY_RATE", 0, 20, 15, 0.1, 1],
["PEEP","cm h2o","peep","SET_TARGET_","PEEP", 0, 100, 15, 0.1, 1],
["Inhale Time", "s", "inhale_time", "SET_TARGET_", "INHALE_TIME", 0, 20, 1, 0.1, 1],
["IE Ratio", "", "ie_ratio", "SET_TARGET_", "IE_RATIO", 0, 1, 0.338, 0.001, 3],
["Inhale Trigger Sensitivity","","inhale_trigger_threshold","SET_TARGET_","INHALE_TRIGGER_THRESHOLD", 0, 20, 5, 0.2, 1],
["Exhale Trigger Sensitivity","","exhale_trigger_threshold","SET_TARGET_","EXHALE_TRIGGER_THRESHOLD", 0, 50, 25, 0.2, 1],
["Inhale Pressure","","inspiratory_pressure","SET_TARGET_","INSPIRATORY_PRESSURE", 10, 50, 17, 1, 0],
["Inhale Volume", "", "volume", "SET_TARGET_", "VOLUME", 200, 800, 400, 20, 0],
["Percentage O2", "", "fiO2_percent", "SET_TARGET_", "FIO2_PERCENT", 20, 100, 21, 1, 0]],
"radioSettings": ["Inhale Time", "IE Ratio"],
"enableDict":{"PC/AC":[1, 1,0, 1, 1, 0, 1, 0, 1], "PC/AC-PRVC":[1, 1,1, 0, 1, 0, 1, 1, 1], "PC-PSV":[1, 1,1, 0, 1, 0, 1, 0, 1], "CPAP":[1, 1,0, 1, 1, 0, 1, 0, 1]},
"mainPageSettings": ["Inhale Pressure", "Respiratory Rate", "Inhale Time", "IE Ratio", "Percentage O2" ]
}
\ No newline at end of file
{"settings":[
["Name", "/min", "name", "SET_PERSONAL", "NAME", "Goedkoop Van Tilator"],
["Patient ID", "s", "patient_id", "SET_PERSONAL", "PATIENT_ID", "11235813FIB"],
["Age", "years", "age", "SET_PERSONAL", "AGE", 0, 130, 25, 1, 0],
["Sex", "", "sex", "SET_PERSONAL", "SEX", "X"],
["Weight", "kg", "weight", "SET_PERSONAL", "WEIGHT", 20, 250, 60, 1, 0],
["Height", "cms", "height", "SET_PERSONAL", "HEIGHT",20, 250, 160, 1, 0]
],
"textBoxes": ["Name", "Patient ID", "Sex"]
}
\ No newline at end of file
{"calibration": {"label": "calibration", "last_performed": 1621426912, "cmd_code": "calib_rate"}, "leak_test": {"label": "Leak Test", "last_performed": 1621426913, "cmd_code": "leak_test"}, "maintenance": {"label": "maintenance", "last_performed": 1621426914, "cmd_code": "main_tenance"}}
......@@ -16,7 +16,7 @@
"layout_label_measurements": "Measurements",
"button_label_main_normal": "Normal",
"button_label_main_detailed": "Detailed",
"ui_window_title": "HEV NativeUI",
"ui_window_title": "HEV NativeUI v{version}",
"measurement_label_plateau_pressure": "P<sub>PLATEAU</sub> [cmH<sub>2</sub>O]",
"measurement_label_respiratory_rate": "RR",
"measurement_label_fio2_percent": "FIO<sub>2</sub> [%]",
......
......@@ -16,12 +16,12 @@
"layout_label_measurements": "Medicoes",
"button_label_main_normal": "Normal",
"button_label_main_detailed": "Detalhado",
"ui_window_title": "HEV NativeUI",
"ui_window_title": "HEV NativeUI v{version}",
"measurement_label_plateau_pressure": "P<sub>Plato</sub> [cmH<sub>2</sub>O]",
"measurement_label_respiratory_rate": "FREQ<sub>RESP</sub>",
"measurement_label_fio2_percent": "FIO<sub>2</sub> [%]",
"measurement_label_fio2_percent": "FIO<sub>2</sub> [%]",
"measurement_label_exhaled_tidal_volume": "VOL<sub>EXAL</sub> [mL]",
"measurement_label_exhaled_minute_volume": "MVE [<sup>L</sup>/<sub>min</sub>]",
"measurement_label_exhaled_minute_volume": "MVE [<sup>L</sup>/<sub>min</sub>]",
"measurement_label_peep": "PEEP [cmH<sub>2</sub>O]",
"measurement_label_inhale_exhale_ratio": "I:E",
"measurement_label_mean_airway_pressure": "P<sub>MEDIO</sub> [cmH<sub>2</sub>O]",
......
......@@ -20,13 +20,9 @@ class selectorButton(QtWidgets.QPushButton):
def __init__(self, NativeUI, *args, **kwargs):
super(selectorButton, self).__init__(*args, **kwargs)
self.setFont(NativeUI.text_font)
style = (
"QPushButton{"
" font-size: " + NativeUI.text_size + ";"
"}"
"QPushButton[selected='0']{"
" font-size: " + NativeUI.text_size + ";"
" color: " + NativeUI.colors["page_foreground"].name() + ";"
" background-color: "
+ NativeUI.colors["button_background_enabled"].name()
......@@ -34,7 +30,6 @@ class selectorButton(QtWidgets.QPushButton):
" border:none"
"}"
"QPushButton[selected='1']{"
" font-size: " + NativeUI.text_size + ";"
" color: " + NativeUI.colors["page_background"].name() + ";"
" background-color:"
+ NativeUI.colors["button_foreground_disabled"].name()
......
......@@ -14,6 +14,12 @@ __status__ = "Prototype"
from PySide2 import QtWidgets, QtGui, QtCore
from widget_library.ok_cancel_buttons_widget import OkButtonWidget, CancelButtonWidget
from widget_library.expert_handler import ExpertHandler
from mode_widgets.personal_handler import PersonalHandler
from mode_widgets.mode_handler import ModeHandler
from mode_widgets.clinical_handler import ClinicalHandler
import logging
# from global_widgets.global_ok_cancel_buttons import okButton, cancelButton
import sys
......@@ -24,32 +30,20 @@ class SetConfirmPopup(QtWidgets.QDialog):
"""Popup called when user wants to send new values to microcontroller.
This popup shows changes and asks for confirmation"""
def __init__(self, parentTemplate, NativeUI, setList, commandList, *args, **kwargs):
ExpertSend = QtCore.Signal()
ModeSend = QtCore.Signal()
PersonalSend = QtCore.Signal()
ClinicalSend = QtCore.Signal()
def __init__(self, NativeUI, *args, **kwargs):
super().__init__(*args, **kwargs)
# self.setStyleSheet("background-color:rgba(255,0,255,50%);color:rgb(0,255,0)")
self.NativeUI = NativeUI
if setList == []:
setList = ["no values were set"]
self.parentTemplate = parentTemplate
self.commandList = commandList
self.handler = None
listWidget = QtWidgets.QListWidget()
for item in setList:
listItem = QtWidgets.QListWidgetItem(item)
listItem.setFlags(QtCore.Qt.NoItemFlags)
listWidget.addItem(listItem)
# size = QtWidgets.QSize()
# s.setHeight(super(qtWidgets.QListWidget,listWidget).sizeHint().height())
# listWidget.setStyleSheet('background-color:black;font:16pt; color:white; border:none')
# self.setWindowOpacity(0.1)
# self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
listWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
listWidget.setFixedHeight(
listWidget.sizeHintForRow(0) * listWidget.count() + 10
)
listWidget.setFixedWidth(listWidget.sizeHintForColumn(0) * listWidget.count())
self.listWidget = QtWidgets.QListWidget()
self.listWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
buttonHLayout = QtWidgets.QHBoxLayout()
......@@ -60,32 +54,63 @@ class SetConfirmPopup(QtWidgets.QDialog):
self.cancelButton = CancelButtonWidget(self.NativeUI)
self.cancelButton.setEnabled(True)
self.cancelButton.pressed.connect(self.cancel_button_pressed)
buttonHLayout.addWidget(self.cancelButton)
vlayout = QtWidgets.QVBoxLayout()
vlayout.addWidget(listWidget)
vlayout.addWidget(self.listWidget)
vlayout.addLayout(buttonHLayout)
self.setLayout(vlayout)
self.setWindowFlags(
QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint
) # no window title
self.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
self.setWindowOpacity(0.5)
# self.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
# self.setWindowOpacity(0.5)
def populatePopup(self, handlerWidget, messageList):
self.handler = handlerWidget
self.clearPopup()
if messageList == []:
messageList = ["no values were set"]
for item in messageList:
listItem = QtWidgets.QListWidgetItem(item)
listItem.setFlags(QtCore.Qt.NoItemFlags)
self.listWidget.addItem(listItem)
self.listWidget.setFixedHeight(
self.listWidget.sizeHintForRow(0) * self.listWidget.count() + 10
)
self.listWidget.setFixedWidth(
self.listWidget.sizeHintForColumn(0) * self.listWidget.count()
)
self.listWidget.update()
self.update()
def clearPopup(self):
self.listWidget.clear()
self.commandList = []
def ok_button_pressed(self):
"""Send commands when ok button is clicked"""
self.parentTemplate.liveUpdating = True
for command in self.commandList:
self.NativeUI.q_send_cmd(*command)
self.close()
# self.parentTemplate.liveUpdating = True
if self.handler is None:
logging.error("Popup ok_button_pressed called before popupatePopup")
return 1
if isinstance(self.handler, ExpertHandler):
self.ExpertSend.emit()
elif isinstance(self.handler, ModeHandler):
self.ModeSend.emit()
elif isinstance(self.handler, PersonalHandler):
self.PersonalSend.emit()
elif isinstance(self.handler, ClinicalHandler):
self.ClinicalSend.emit()
else:
logging.warning("Unrecognised handler type: %s", type(self.handler))
return 0
def cancel_button_pressed(self):
"""Close popup when cancel button is clicked"""
self.close()
return 0
# def cancel_button_pressed(self):
# """Close popup when cancel button is clicked"""
# print("CANCEL BUTTON PRESSED")
# # self.close()
# return 0
class confirmWidget(QtWidgets.QWidget):
......@@ -140,20 +165,8 @@ class confirmPopup(QtWidgets.QWidget):
self.vlayout.setMargin(0)
self.setLayout(self.vlayout)
self.location_on_window()
self.setWindowFlags(
QtCore.Qt.FramelessWindowHint
| QtCore.Qt.Dialog
| QtCore.Qt.WindowStaysOnTopHint
) # no window title
self.setStyleSheet("background-color:green;")
self.timer = QtCore.QTimer()
self.timer.setInterval(100) # just faster than 60Hz
self.timer.timeout.connect(self.adjustSize)
self.timer.start()
def addConfirmation(self, confirmMessage):
"""Add a confirmation to the popup. Triggered when UI receives a confirmation from the microcontroller"""
self.confirmDict[confirmMessage] = confirmWidget(
......@@ -162,18 +175,9 @@ class confirmPopup(QtWidgets.QWidget):
self.vlayout.addWidget(self.confirmDict[confirmMessage])
return 0
def location_on_window(self):
screen = QtWidgets.QDesktopWidget().screenGeometry()
# widget = self.geometry()
x = screen.width() - screen.width() / 2
y = 0 # screen.height() - widget.height()
self.move(x, y)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widg = SetConfirmPopup(
None, ["test text", "test", "test", "tregfdgdfgd", "experiment"]
)
widg.show()
sys.exit(app.exec_())
# def location_on_window(self):
# screen = QtWidgets.QDesktopWidget().screenGeometry()
# # widget = self.geometry()
# x = screen.width() - screen.width() / 2
# y = 0 # screen.height() - widget.height()
# self.move(x, y)
This diff is collapsed.
......@@ -14,26 +14,34 @@ __status__ = "Prototype"
from PySide2 import QtCore, QtGui, QtWidgets
import os
os.environ["QT_IM_MODULE"] = "qtvirtualkeyboard"
from widget_library.ok_cancel_buttons_widget import OkButtonWidget, CancelButtonWidget
from widget_library.numpad_widget import NumberpadWidget
class TypeValuePopup(QtWidgets.QDialog):
"""Popup takes user input to put in spin box. """
okPressed = QtCore.Signal(str)
cancelPressed = QtCore.Signal()
def __init__(self, NativeUI):
super().__init__()
def __init__(self, NativeUI, *args, **kwargs):
super().__init__(*args, **kwargs)
#self.label_text = label_text
#self.min, self.max, self.initVal, self.step, self.decPlaces = min, max, initVal, step, decPlaces
grid = QtWidgets.QGridLayout()
grid.setSpacing(1)
self.setStyleSheet("border-radius:4px; background-color:black")
self.label = QtWidgets.QLabel()#self.label_text)
self.label.setFont(NativeUI.text_font)
self.label.setStyleSheet('color: ' + NativeUI.colors["page_foreground"].name())
self.lineEdit = QtWidgets.QLineEdit()
self.lineEdit.setText("4")
self.lineEdit.setStyleSheet(
"QLineEdit{"
" font-size: " + NativeUI.text_size + ";"
" background-color: white;"
" border-radius: 4px;"
"}"
......@@ -47,27 +55,105 @@ class TypeValuePopup(QtWidgets.QDialog):
" color: red;"
"}"
)
self.lineEdit.setFont(NativeUI.text_font)
self.lineEdit.setProperty("colour", "1")
self.lineEdit.setAlignment(QtCore.Qt.AlignCenter)
self.lineEdit.saveVal = self.lineEdit.text()
self.lineEdit.saveVal = ''#self.lineEdit.text()
self.lineEdit.setValidator(
QtGui.QDoubleValidator(0.0, 100.0, 2)
) # ensures only doubles can be typed
QtGui.QDoubleValidator(0.0, 100.0, 5)
) # ensures only doubles can be typed, do
# self.lineEdit.textEdited.connect(self.setTextColour(1))
grid.addWidget(self.lineEdit, 0, 0, 1, 2)
self.numberpad = NumberpadWidget(NativeUI)
self.numberpad.numberPressed.connect(self.handle_numberpress)
self.increaseButton = OkButtonWidget(NativeUI)
self.increaseButton.clicked.connect(self.increase_button_clicked)
self.increaseButton.setEnabled(True)
self.decreaseButton = CancelButtonWidget(NativeUI)
self.decreaseButton.clicked.connect(self.decrease_button_clicked)
self.decreaseButton.setEnabled(True)
#grid.addWidget(self.lineEdit, 0, 0, 1, 2)
hlayout = QtWidgets.QHBoxLayout()
hlayout.addWidget(self.decreaseButton)
hlayout.addWidget(self.lineEdit)
hlayout.addWidget(self.increaseButton)
hlayout2 = QtWidgets.QHBoxLayout()
self.okButton = OkButtonWidget(NativeUI)
self.okButton.setEnabled(True)
grid.addWidget(self.okButton, 1, 0)
self.okButton.pressed.connect(self.handle_ok_press)
hlayout2.addWidget(self.okButton)
#grid.addWidget(self.okButton, 1, 0)
self.cancelButton = CancelButtonWidget(NativeUI)
self.cancelButton.setEnabled(True)
grid.addWidget(self.cancelButton, 1, 1)
self.setLayout(grid)
hlayout2.addWidget(self.cancelButton)
#grid.addWidget(self.cancelButton, 1, 1)
vlayout = QtWidgets.QVBoxLayout()
vlayout.addWidget(self.label)
vlayout.addLayout(hlayout)
vlayout.addWidget(self.numberpad)
vlayout.addLayout((hlayout2))
self.setLayout(vlayout)
self.setWindowFlags(
QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint
) # no window title
def getValue(self):
def handle_ok_press(self):
val = self.lineEdit.text()
self.currentWidg.setValue(float(val))
self.close()
self.currentWidg.manualChanged.emit()
def populatePopup(self, currentWidg):
self.currentWidg = currentWidg
self.label_text, self.min, self.max, self.initVal, self.step, self.decPlaces = currentWidg.label_text, currentWidg.min, currentWidg.max, currentWidg.initVal, currentWidg.step, currentWidg.decPlaces
self.label.setText(self.label_text)
self.lineEdit.setText(str(currentWidg.value()))
def handle_numberpress(self, symbol):
"""Handle number pad button press. Put button value in line edit text, and handle inputs
outside accepted range or number of decimal places. Handle backspace"""
oldText = self.lineEdit.text()
if symbol.isnumeric() or (symbol == '.'):
newText = oldText + symbol
if float(newText) > self.max:
newText = str(self.max)
elif float(newText) < self.min:
newText = str(self.min)
elif '.' in newText:
if len(newText.split('.')[1]) > self.decPlaces:
newText = oldText
self.lineEdit.setText(newText)
elif symbol == '<':
self.lineEdit.setText(oldText[0:-1])
def increase_button_clicked(self):
"""Handle increase step button click"""
currentVal = self.get_value()
newVal = round(float(currentVal) + self.step, self.decPlaces)
if newVal >= self.max:
newVal = self.max
self.lineEdit.setText(str(newVal))
return 0
def decrease_button_clicked(self):
"""Handle decrease step button click"""
currentVal = self.get_value()
newVal = round(float(currentVal) - self.step, self.decPlaces)
if newVal <= self.min:
newVal = self.min
self.lineEdit.setText(str(newVal))
return 0
def get_value(self):
return self.lineEdit.text()
......@@ -19,6 +19,8 @@ from widget_library.ok_cancel_buttons_widget import OkButtonWidget, CancelButton
class TabModeswitchButton(QtWidgets.QWidget):
modeSwitched = QtCore.Signal(str)
def __init__(self, NativeUI, *args, **kwargs):
super().__init__(*args, **kwargs)
......@@ -26,6 +28,13 @@ class TabModeswitchButton(QtWidgets.QWidget):
layout = QtWidgets.QHBoxLayout(self)
self.label = QtWidgets.QLabel("Mode: ")
self.label.setFont(NativeUI.text_font)
self.label.setStyleSheet(
"background-color:" + NativeUI.colors["page_background"].name() + ";"
"border: none;"
"color:" + NativeUI.colors["page_foreground"].name() + ";"
)
self.switchButton = QtWidgets.QPushButton(self.NativeUI.modeList[0])
layout.addWidget(self.label)
layout.addWidget(self.switchButton)
......@@ -33,18 +42,31 @@ class TabModeswitchButton(QtWidgets.QWidget):
self.mode_popup = False
self.switchButton.pressed.connect(self.switch_button_pressed)
# self.mode_popup.okbutton.pressed.connect(self.changeText)
def update_mode(self, mode):
print("updating mode")
print(mode)
self.switchButton.setText(mode)
# self.mode_popup.update_mode(mode)
def switch_button_pressed(self):
if self.mode_popup == False:
self.mode_popup = modeswitchPopup(self.NativeUI)
self.mode_popup.okbutton.pressed.connect(self.changeText)
else:
self.mode_popup.radioButtons[self.NativeUI.currentMode].click()
self.mode_popup.show()
def changeText(self):
self.switchButton.setText(self.mode_popup.mode)
self.modeSwitched.emit(self.mode_popup.mode)
def set_size(self, x: int, y: int, spacing=10) -> int:
self.setFixedSize(x, y)
return 0
class modeswitchPopup(QtWidgets.QWidget):
......@@ -53,14 +75,38 @@ class modeswitchPopup(QtWidgets.QWidget):
self.NativeUI = NativeUI
self.settingsList = [
"Respiratory Rate",
"Inhale Time",
"IE Ratio",
"Inhale Trigger Sensitivity",
"Exhale Trigger Sensitivity",
"Inhale Pressure",
"Inhale Volume",
"Percentage O2",
[
"Respiratory Rate",
"/min",
"respiratory_rate",
"SET_TARGET_",
"RESPIRATORY_RATE",
],
["Inhale Time", "s", "inhale_time", "SET_TARGET_", "INHALE_TIME"],
["IE Ratio", "", "ie_ratio", "SET_TARGET_", "IE_RATIO"],
[
"Inhale Trigger Sensitivity",
"",
"inhale_trigger_threshold",
"SET_TARGET_",
"INHALE_TRIGGER_THRESHOLD",
],
[
"Exhale Trigger Sensitivity",
"",
"exhale_trigger_threshold",
"SET_TARGET_",
"EXHALE_TRIGGER_THRESHOLD",
],
[
"Inhale Pressure",
"",
"inspiratory_pressure",
"SET_TARGET_",
"INSPIRATORY_PRESSURE",
],
["Inhale Volume", "", "volume", "SET_TARGET_", "VOLUME"],
["Percentage O2", "", "fiO2_percent", "SET_TARGET_", "FIO2_PERCENT"],
] # self.NativeUI.modes_view.modeTab.settingsList
modeList = self.NativeUI.modeList
......@@ -77,6 +123,8 @@ class modeswitchPopup(QtWidgets.QWidget):
self.radioButtons[mode] = button
vradioLayout.addLayout(hlayout)
button.pressed.connect(lambda i=button: self.update_settings_data(i))
if mode == self.NativeUI.currentMode:
button.click()
groupBox.setLayout(vradioLayout)
## Values display
......@@ -100,7 +148,7 @@ class modeswitchPopup(QtWidgets.QWidget):
vlayout2.addWidget(initVal)
vlayout3.addWidget(newVal)
for settings in self.settingsList:
namelabel = QtWidgets.QLabel(settings)
namelabel = QtWidgets.QLabel(settings[0])
namelabel.setAlignment(QtCore.Qt.AlignRight)
vlayout1.addWidget(namelabel)
......@@ -161,28 +209,33 @@ class modeswitchPopup(QtWidgets.QWidget):
self.NativeUI.widgets.page_buttons.set_pressed(["modes_button"])
# Switch to the specific mode tab
for button in self.NativeUI.widgets.mode_settings_tab.buttonWidgets:
print(button.text())
print(mode)
for button in self.NativeUI.widgets.modes_page.widget_list[
0
].button_list: # mode_settings_tab.buttonWidgets:
if mode in button.text():
button.click()
self.NativeUI.widgets.modes_page.widget_list[0].setTab(button)
# Close the popup
self.close()
def update_settings_data(self, button):
self.spinDict = self.NativeUI.widgets.mode_settings_tab.spinDict
self.spinDict = self.NativeUI.widgets.mode_handler.spinDict
self.mode = button.text() # .replace("/", "_").replace("-", "_")
data = self.NativeUI.get_db("targets")
for settings, currentLabel, newLabel in zip(
self.settingsList, self.currentLabelList, self.newLabelList
):
currentVal = self.spinDict[
self.NativeUI.currentMode # .replace("/", "_").replace("-", "_")
][settings].get_value()
"spin_" + self.NativeUI.currentMode + "_" + settings[2]
].get_value()
# currentVal = self.spinDict[
# self.NativeUI.currentMode # .replace("/", "_").replace("-", "_")
# ][settings].get_value()
currentLabel.setText(str(round(currentVal, 4)))
setVal = self.spinDict[self.mode][settings].get_value()
setVal = self.spinDict["spin_" + self.mode + "_" + settings[2]].get_value()
newLabel.setText(str(round(setVal, 4)))
print("done")
def ok_button_pressed(self):
if self.NativeUI.currentMode == self.mode:
......@@ -194,16 +247,21 @@ class modeswitchPopup(QtWidgets.QWidget):
self.NativeUI.currentMode = self.mode
self.close()
# ensure main page buttons display IE Ratio or Inhale Time as enabled
if self.NativeUI.widgets.mode_settings_tab.tabsDict[
self.mode
].radioButtonRat.isChecked():
# self.NativeUI.main_view.tab_spin.setStackWidget("IE Ratio")
self.NativeUI.widgets.spin_buttons.setStackWidget("IE Ratio")
else:
# self.NativeUI.main_view.tab_spin.setStackWidget("Inhale Time")
self.NativeUI.widgets.spin_buttons.setStackWidget("Inhale Time")
# if self.NativeUI.widgets.mode_settings_tab.tabsDict[
# self.mode
# ].radioButtonRat.isChecked():
# # self.NativeUI.main_view.tab_spin.setStackWidget("IE Ratio")
# self.NativeUI.widgets.spin_buttons.setStackWidget("IE Ratio")
# else:
# # self.NativeUI.main_view.tab_spin.setStackWidget("Inhale Time")
# self.NativeUI.widgets.spin_buttons.setStackWidget("Inhale Time")
# self.modeSwitched.emit()
return 0
def cancel_button_pressed(self):
self.close()
return 0
def update_mode(self, mode):
self.mode_popup.radioButtons[mode].click()
......@@ -50,10 +50,10 @@ class TabStartStopStandbyButtons(QtWidgets.QWidget):
button.setStyleSheet(
"background-color:" + NativeUI.colors["background_enabled"].name() + ";"
"border-color:" + NativeUI.colors["page_foreground"].name() + ";"
"font-size:" + NativeUI.text_size + ";"
"color:" + NativeUI.colors["page_foreground"].name() + ";"
"border:none"
)
button.setFont(NativeUI.text_font)
button.setFixedSize(self.__button_size)
self.setLayout(layout)
"""
alarm_handler.py
"""
from handler_library.handler import PayloadHandler
from PySide2.QtCore import QObject
class AlarmHandler(PayloadHandler, QObject):
"""
Subclass of the PayloadHandler class (handler.py) to handle alarm data.
Inherits from QObject to give us access to pyside2's signal class.
"""
def __init__(self):
super().__init__()
QObject.__init__(self)
import logging
from handler_library.handler import PayloadHandler
from PySide2.QtCore import Signal, QObject
class BatteryHandler(PayloadHandler):
"""
Subclass of the PayloadHandler class (handler.py) to handle alarm data.
"""
UpdateBatteryDisplay = Signal(dict)
def __init__(self, *args, **kwargs):
super().__init__(["BATTERY"], *args, **kwargs)
def active_payload(self, *args, **kwargs) -> int:
"""
When battery information is set, interprets it to construct the battery status
and emits the UpdateBatteryDisplay signal containing that status as a dict.
"""
new_status = {}
battery_data = self.get_db()
# Update new_status
try:
new_status["on_mains_power"] = bool(battery_data["ok"])
except KeyError:
logging.debug("Keyerror in battery payload: 'ok'")
try:
new_status["on_battery_power"] = bool(battery_data["bat"])
except KeyError:
logging.debug("Keyerror in battery payload: 'bat'")
try:
new_status["battery_percent"] = self.compute_battery_percent(battery_data)
except KeyError:
logging.debug("Keyerror in battery payload: 'bat85'")
try:
if bool(battery_data["prob_elec"]):
new_status["electrical_problem"] = "ERROR ELEC."
else:
new_status["electrical_problem"] = None
except KeyError:
logging.debug("Keyerror in battery payload: 'prob_elec'")
# Sanity checks
if new_status["on_mains_power"] == new_status["on_battery_power"]:
# If there is conflicting information w.r.t. power source, report a problem
new_status["on_mains_power"] = False
new_status["on_battery_power"] = False
new_status["electrical_problem"] = "ERROR ELEC."
self.UpdateBatteryDisplay.emit(new_status)
return 0
def compute_battery_percent(self, battery_data: dict) -> float:
"""
Determine the current battery percentage from the information in battery_data.
As of 17/03/21 battery payloads only contain enough information to
determine if the battery is above or below 85% battery life.
Unless provided with specific information to the contrary, assume that the
battery is on 0% so that we should never overestimate how much remains.
"""
if battery_data["bat85"] == 1:
return 85.0
elif battery_data["bat85"] == 0:
return 0.0
else:
raise TypeError(
"Battery Percentage (entry 'bat85' in the battery payload) is not 1 or 2."
)
from handler_library.handler import PayloadHandler
from PySide2.QtCore import Signal
import numpy as np
from threading import Lock
class DataHandler(PayloadHandler):
UpdatePlots = Signal(dict)
def __init__(self, *args, plot_history_length=500, **kwargs):
super().__init__(["DATA", "CYCLE"], *args, **kwargs)
self.__plot_history_length = plot_history_length
self.__plots_database = {
"data": np.zeros((plot_history_length, 4)),
"timestamp": list(el * (-1) for el in range(plot_history_length))[::-1],
"pressure": list(0 for _ in range(plot_history_length)),
"flow": list(0 for _ in range(plot_history_length)),
"volume": list(0 for _ in range(plot_history_length)),
"cycle_pressure": list(0 for _ in range(plot_history_length)),
"cycle_flow": list(0 for _ in range(plot_history_length)),
"cycle_volume": list(0 for _ in range(plot_history_length)),
"pressure_axis_range": [0, 20],
"flow_axis_range": [-40, 80],
"volume_axis_range": [0, 80],
}
self.__plot_lock = Lock()
self.__cycle_index: list = [
plot_history_length,
plot_history_length,
plot_history_length,
]
def active_payload(self, payload, *args, **kwargs):
"""
Take the raw payload information into conveniently plotable forms and store them
in self.__plots_database.
"""
raw_data = self.get_db()
if payload["type"] == "DATA":
# The earliest index to plot for cycle plots needs to move back to keep pace
# with the data.
self.__cycle_index = [
index - 1 if (index - 1 >= 0) else 0 for index in self.__cycle_index
]
with self.__plot_lock:
# Remove the oldest data and add the new data to the other end of the
# array.
self.__plots_database["data"] = np.append(
np.delete(self.__plots_database["data"], 0, 0),
[
[
raw_data["timestamp"],
raw_data["pressure_patient"],
raw_data["flow"],
raw_data["volume"],
]
],
axis=0,
)
# subtract latest timestamp and scale to seconds.
self.__plots_database["timestamp"] = np.true_divide(
np.subtract(
self.__plots_database["data"][:, 0],
self.__plots_database["data"][-1, 0],
),
1000,
)
# Pull out the specific properties we want to plot.
self.__plots_database["pressure"] = self.__plots_database["data"][:, 1]
self.__plots_database["flow"] = self.__plots_database["data"][:, 2]
self.__plots_database["volume"] = self.__plots_database["data"][:, 3]
self.__plots_database["cycle_pressure"] = self.__plots_database["data"][
self.__cycle_index[0] :, 1
]
self.__plots_database["cycle_flow"] = self.__plots_database["data"][
self.__cycle_index[0] :, 2
]
self.__plots_database["cycle_volume"] = self.__plots_database["data"][
self.__cycle_index[0] :, 3
]
elif payload["type"] == "CYCLE":
self.__cycle_index.pop(0)
self.__cycle_index.append(self.__plot_history_length)
return 0
def send_update_plots_signal(self) -> int:
"""
construct a dictionary of only the properties needed for plotting and emit the
UpdatePlots signal containing it.
"""
outdict = {}
with self.__plot_lock:
for key in [
"flow",
"pressure",
"timestamp",
"volume",
"cycle_flow",
"cycle_pressure",
"cycle_volume",
]:
outdict[key] = self.__plots_database[key]
self.UpdatePlots.emit(outdict)
return 0
"""
handler.py
"""
from threading import Lock
from global_widgets.global_spinbox import labelledSpin
from widget_library.ok_cancel_buttons_widget import OkButtonWidget, CancelButtonWidget, OkSendButtonWidget
from PySide2.QtCore import QObject
from PySide2.QtWidgets import QRadioButton
class GenericDataHandler(QObject):
"""
Base class for non-payload data handlers.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__database = {}
self.__lock = Lock()
def set_db(self, data: dict) -> int:
"""
Copy the contents of 'data' to the internal database.
"""
with self.__lock:
for key in data:
self.__database[key] = data[key]
self.on_data_set()
return 0
def get_db(self) -> dict:
"""
Return the content of the __database dictionary.
"""
with self.__lock:
return dict(self.__database)
def on_data_set(self):
"""
Overridable function called after recieving new data.
"""
pass
class PayloadHandler(GenericDataHandler):
"""
Base class for the payload data handlers.
"""
def __init__(self, payload_types: list, *args, **kwargs):
super().__init__(*args, **kwargs)
for key in payload_types:
if not isinstance(key, str):
raise TypeError(
"payload types must be of type 'str', not %s", type(key)
)
self.__database = {}
self.__lock = Lock()
self.__payload_types = payload_types
def set_db(self, payload: dict) -> int:
"""
If the provided database is of the correct type, copy its contents to the database
"""
if payload["type"] not in self.__payload_types:
return 1
with self.__lock:
for key in payload[payload["type"]]:
self.__database[key] = payload[payload["type"]][key]
self.active_payload(payload)
return 0
def get_db(self) -> dict:
"""
Return the content of the __database dictionary.
"""
with self.__lock:
return dict(self.__database)
def active_payload(self, payload: dict):
"""
Overridable function called after recieving new data. Passes in the full payload
so that we have access to the full context of the information.
"""
pass
"""
measurement_handler.py
"""
from handler_library.handler import PayloadHandler
from PySide2.QtCore import Signal
import logging
class MeasurementHandler(PayloadHandler):
"""
Subclass of the PayloadHandler class (handler.py) to handle cycle and readback data.
"""
UpdateMeasurements = Signal(dict)
def __init__(self, *args, **kwargs):
super().__init__(["CYCLE", "READBACK"], *args, **kwargs)
def active_payload(self, *args, **kwargs) -> int:
cycle_data = self.get_db()
outdict = {}
for key in [
"plateau_pressure",
"respiratory_rate",
"fiO2_percent",
"exhaled_tidal_volume",
"exhaled_minute_volume",
"peak_inspiratory_pressure",
"mean_airway_pressure",
"inhaled_tidal_volume",
"inhaled_minute_volume",
"peep",
"inhale_exhale_ratio",
]:
try:
outdict[key] = cycle_data[key]
except KeyError:
logging.debug("Invalid key %s in measurement database", key)
self.UpdateMeasurements.emit(outdict)
return 0
"""
personal_handler.py
"""
from handler_library.handler import PayloadHandler
from PySide2.QtCore import Signal
class PersonalHandler(PayloadHandler):
"""
Subclass of the PayloadHandler class (handler.py) to handle personal data.
Adds the UpdatePersonalDisplay signal designed to convey information to be displayed
to the display widget.
"""
UpdatePersonalDisplay = Signal(dict)
def __init__(self, *args, **kwargs):
super().__init__(["PERSONAL"], *args, **kwargs)
def active_payload(self, *args, **kwargs) -> int:
"""
When the personal data is updated, extract those parameters needed for display
and emit the UpdatePersonalDisplay signal.
"""
current_data = self.get_db()
outdict = {}
for key in ["name", "patient_id", "age", "sex", "height", "weight"]:
outdict[key] = current_data[key]
self.UpdatePersonalDisplay.emit(outdict)
return 0
from global_widgets.global_spinbox import labelledSpin
from widget_library.ok_cancel_buttons_widget import OkButtonWidget, CancelButtonWidget, OkSendButtonWidget
#from global_widgets.global_send_popup import SetConfirmPopup
from widget_library.spin_buttons_widget import SpinButton
from PySide2 import QtWidgets, QtGui, QtCore
from handler_library.handler import PayloadHandler
import logging
import json
class ClinicalHandler(PayloadHandler):
#modeSwitched = QtCore.Signal(str)
UpdateClinical = QtCore.Signal(dict)
OpenPopup = QtCore.Signal(PayloadHandler, list)
#settingToggle = QtCore.Signal(str)
def __init__(self, NativeUI, *args, **kwargs):
super().__init__(['TARGET'],*args, **kwargs)
#super(TabModes, self).__init__(NativeUI, *args, **kwargs)
self.NativeUI = NativeUI
self.limSpinDict = {}
self.setSpinDict = {}
self.buttonDict = {}
self.radioDict = {}
self.commandList = []
self.manuallyUpdated = False
self.valueDict = {}
with open("NativeUI/configs/clinical_config.json") as json_file:
clinicalDict = json.load(json_file)
self.singleThresholds = clinicalDict["SingleThresholds"]
self.absoluteLimits = clinicalDict["AbsoluteLimits"]
self.limit_to_mode_dict = {}
self.relevantKeys = []
for setting in clinicalDict['settings']:
if len(setting) == 3:
limit_code = setting[0][2]
mode_code = setting[1][2]
mode_minimum = setting[1][5]
mode_maximum = setting[1][6]
limit_minimum = setting[0][5]
limit_maximum = setting[-1][6]
self.limit_to_mode_dict[limit_code] = [mode_code, mode_minimum, mode_maximum, limit_minimum, limit_maximum]
self.relevantKeys.append(setting[1][2])
def add_widget(self, widget, key: str):
if isinstance(widget, labelledSpin):
if 'min' in key or 'max' in key:
self.limSpinDict[key] = widget
elif 'set' in key:
self.setSpinDict[key] = widget
self.valueDict[key] = widget.get_value()
if isinstance(widget, OkButtonWidget) or isinstance(widget, CancelButtonWidget) or isinstance(widget, OkSendButtonWidget):
self.buttonDict[key] = widget
if isinstance(widget, QtWidgets.QRadioButton):
self.radioDict[key] = widget
def active_payload(self, *args) -> int:
target_data = self.get_db()
outdict = {}
for key in self.relevantKeys:
try:
outdict[key] = target_data[key]
except KeyError:
logging.debug("Invalid key %s in measurement database", key)
self.UpdateClinical.emit(outdict)
return 0
def handle_okbutton_click(self):
message, command = [], []
for key, widget in dict(self.limSpinDict, **self.setSpinDict).items():
if widget.manuallyUpdated:
setVal = widget.get_value()
if ('set' not in key):
setkey = key.replace('min', 'set').replace('max','set')
if (widget.label in self.absoluteLimits):
multiplier = 1
else:
multiplier = self.setSpinDict[setkey].get_value()/100
setVal = self.setSpinDict[setkey].get_value() + setVal*multiplier
setVal = round(setVal,widget.decPlaces)
message.append("set" + key + " to " + str(setVal))
command.append(
[
widget.cmd_type,
widget.cmd_code,
setVal,
]
)
# create a signal emitting message, command, handler identifier - in nativeui connect to a popup widget
# command sending should occur in handler
self.commandList = command
self.OpenPopup.emit(self,message)
def sendCommands(self):
if self.commandList == []:
a=1
else:
for command in self.commandList:
self.NativeUI.q_send_cmd(*command)
self.commandSent()
return 0
def handle_cancelbutton_click(self):
for key, widget in dict(self.limSpinDict, **self.setSpinDict).items():
widget.manuallyUpdated = False
widget.set_value(self.valueDict[key])
self.active_payload()
self.refresh_button_colour()
def commandSent(self):
self.commandList = []
for key, widget in dict(self.limSpinDict, **self.setSpinDict).items():
widget.manuallyUpdated = False
self.valueDict[key] = widget.get_value()
widget.set_value(widget.get_value())
self.active_payload()
self.refresh_button_colour()
def handle_manual_change(self, changed_spin_key):
self.active_payload()
self.refresh_button_colour()
def setpoint_changed(self, widget):
"""Respond to change in operational settings to modify alarm limits. If setpoint is close to an absolute maximum
or minimum the alarm limits should respond.
Takes the modified widget, uses its tag to identify corresponding alarm limits"""
cmd_code = widget.tag
for key, infoList in self.limit_to_mode_dict.items():
if cmd_code in infoList[0]: # find entry in dictionary corresponding to the modified widget
setValue = widget.get_value()
minValue = float(infoList[1])
maxValue = float(infoList[2])
limMin = float(infoList[3])
limMax = float(infoList[4])
attrName = 'clinical_spin_' + key
minLimitWidget = self.limSpinDict[attrName + '_min']
maxLimitWidget = self.limSpinDict[attrName + '_max']
if widget is not self.setSpinDict[attrName + '_set']: # handle incoming value from 'set point' spin boxes elsewhere in ui
if isinstance(widget, labelledSpin):
if self.NativeUI.currentMode.replace('/','_').replace('-','_') in widget.cmd_type:
self.setSpinDict[attrName + '_set'].simpleSpin.set_value(
widget.get_value())
elif isinstance(widget, SpinButton):
self.setSpinDict[attrName + '_set'].simpleSpin.set_value(widget.get_value())
if widget.label in self.absoluteLimits:
denominator = 100 # just get difference if looking for absolute limit
else:
denominator = setValue # get percentage if looking for percentage
pct_to_max = 100*(maxValue - setValue)/denominator
pct_to_min = 100*(minValue -setValue)/denominator
# print('maximum is ' + str(limMax))
# print('pct to max is ' + str(pct_to_max))
# print('maxval is ' + str(maxValue))
# print('setVal is ' + str(setValue))
# print('denom is ' + str(denominator))
if round(pct_to_max,4) <= round(limMax,4): # round to avoid errors with floating point numbers
maxLimitWidget.set_maximum(pct_to_max)
elif round(pct_to_min,4) >= round(limMin,4):
minLimitWidget.set_minimum(pct_to_min)
else:
maxLimitWidget.set_maximum(10)
minLimitWidget.set_minimum(-10)
self.refresh_button_colour()
def refresh_button_colour(self):
'''Refresh button colour based on whether there are any manually updated spin widgets or not'''
self.manuallyUpdated = False
for spin in dict(self.limSpinDict, **self.setSpinDict).values():
self.manuallyUpdated = self.manuallyUpdated or spin.manuallyUpdated
for button in self.buttonDict:
if isinstance(self.buttonDict[button], OkSendButtonWidget):
self.buttonDict[button].setColour(str(int(True)))
else:
self.buttonDict[button].setColour(str(int(self.manuallyUpdated)))
def get_mode(self, key: str):
for mode in self.modeList:
if mode in key:
return mode
from global_widgets.global_spinbox import labelledSpin
from widget_library.ok_cancel_buttons_widget import OkButtonWidget, CancelButtonWidget, OkSendButtonWidget
#from global_widgets.global_send_popup import SetConfirmPopup
from widget_library.spin_buttons_widget import SpinButton
from PySide2 import QtWidgets, QtGui, QtCore
from handler_library.handler import PayloadHandler
import logging
import json
class ModeHandler(PayloadHandler):
modeSwitched = QtCore.Signal(str)
UpdateModes = QtCore.Signal(dict)
OpenPopup = QtCore.Signal(PayloadHandler, list)
settingToggle = QtCore.Signal(str)
def __init__(self, NativeUI, *args, **kwargs):
super().__init__(['TARGET'],*args, **kwargs)
#super(TabModes, self).__init__(NativeUI, *args, **kwargs)
self.NativeUI = NativeUI
self.spinDict = {}
self.buttonDict = {}
self.radioDict = {}
self.commandList = []
self.mainSpinDict = {}
self.mainButtonDict = {}
self.modeList = ["PC/AC", "PC/AC-PRVC", "PC-PSV", "CPAP", 'CURRENT']
self.manuallyUpdatedBoolDict = { mode: False for mode in self.modeList }
self.mainManuallyUpdated = False
self.activeMode = self.modeList[0]
with open("NativeUI/configs/mode_config.json") as json_file:
modeDict = json.load(json_file)
self.relevantKeys = [setting[2] for setting in modeDict['settings']]
def add_widget(self, widget, key: str):
if isinstance(widget, labelledSpin):
self.spinDict[key] = widget
if isinstance(widget, OkButtonWidget) or isinstance(widget, CancelButtonWidget) or isinstance(widget, OkSendButtonWidget):
if self.get_mode(key) == 'CURRENT':
self.mainButtonDict[key] = widget
else:
self.buttonDict[key] = widget
if isinstance(widget, QtWidgets.QRadioButton):
self.radioDict[key] = widget
if isinstance(widget, SpinButton):
self.mainSpinDict[key] = widget
def active_payload(self, *args) -> int:
target_data = self.get_db()
outdict = {}
for key in self.relevantKeys:
try:
outdict[key] = target_data[key]
except KeyError:
logging.debug("Invalid key %s in measurement database", key)
self.UpdateModes.emit(outdict)
return 0
def handle_okbutton_click(self, key):
mode = self.get_mode(key)
message, command = [], []
for widget in self.spinDict:
if (mode in widget) and self.spinDict[widget].manuallyUpdated:
setVal = self.spinDict[widget].get_value()
setVal = round(setVal, widget.decPlaces)
message.append("set" + widget + " to " + str(setVal))
command.append(
[
self.spinDict[widget].cmd_type,
self.spinDict[widget].cmd_code,
setVal,
]
)
# create a signal emitting message, command, handler identifier - in nativeui connect to a popup widget
# command sending should occur in handler
self.commandList = command
self.OpenPopup.emit(self,message)
def handle_mainokbutton_click(self):
message, command = [], []
for widget in self.mainSpinDict:
if self.mainSpinDict[widget].manuallyUpdated:
setVal = self.mainSpinDict[widget].get_value()
setVal = round(setVal, widget.decPlaces)
message.append("set" + widget + " to " + str(setVal))
command.append(
[
self.mainSpinDict[widget].cmd_type,
self.mainSpinDict[widget].cmd_code,
setVal,
]
)
# create a signal emitting message, command, handler identifier - in nativeui connect to a popup widget
# command sending should occur in handler
self.commandList = command
self.OpenPopup.emit(message)
def sendCommands(self):
if self.commandList == []:
a=1
else:
for command in self.commandList:
self.NativeUI.q_send_cmd(*command)
self.modeSwitched.emit(self.activeMode)
self.commandSent()
return 0
def commandSent(self):
self.commandList = []
for widget in self.spinDict:
if self.activeMode in widget:
self.spinDict[widget].manuallyUpdated = False
for widget in self.mainSpinDict:
self.mainSpinDict[widget].manuallyUpdated = False
self.active_payload()
self.refresh_button_colour()
self.refresh_main_button_colour()
def handle_manual_change(self, changed_spin_key):
print('handle manual change')
self.active_payload()
self.refresh_button_colour()
self.refresh_main_button_colour()
def handle_radio_toggled(self, radioButtonState, radioKey):
"""TODO Docstring"""
mode = self.get_mode(radioKey)
spinKey= radioKey.replace('radio', 'spin')
spinBox = self.spinDict[spinKey]
spinBox.setEnabled(radioButtonState)
if mode == self.NativeUI.currentMode:
self.settingToggle.emit(spinBox.label)
def refresh_button_colour(self):
self.manuallyUpdatedBoolDict = { mode: False for mode in self.modeList }
for spin in self.spinDict:
mode = self.get_mode(spin)
if mode == None: continue
self.manuallyUpdatedBoolDict[mode] = self.manuallyUpdatedBoolDict[mode] or self.spinDict[spin].manuallyUpdated
for button in self.buttonDict:
mode = str(self.get_mode(button))
if isinstance(self.buttonDict[button], OkSendButtonWidget) and (mode != self.NativeUI.currentMode):
self.buttonDict[button].setColour(str(int(True)))
else:
self.buttonDict[button].setColour(str(int(self.manuallyUpdatedBoolDict[mode])))
def propagate_modevalchange(self,widget):
for spin in self.mainSpinDict.values():
if spin.tag == widget.tag:
if spin.get_value() != widget.get_value():
spin.set_value(widget.get_value())
for spin in self.spinDict.values():
if spin.tag == widget.tag:
if self.NativeUI.currentMode.replace('/','_').replace('-','_') in spin.cmd_type:
if spin.get_value() != widget.get_value():
spin.simpleSpin.set_value(widget.get_value())
def refresh_main_button_colour(self):
print('refreshing main buttons')
self.manuallyUpdatedBoolDict['CURRENT'] = False
for spin in self.mainSpinDict:
print(spin + 'is ' + str(self.mainSpinDict[spin].manuallyUpdated))
self.manuallyUpdatedBoolDict['CURRENT'] = self.manuallyUpdatedBoolDict['CURRENT'] or self.mainSpinDict[spin].manuallyUpdated
for button in self.mainButtonDict:
self.mainButtonDict[button].setColour(str(int(self.manuallyUpdatedBoolDict['CURRENT'])))
def get_mode(self, key: str):
for mode in self.modeList:
if mode in key:
return mode
from global_widgets.global_spinbox import labelledSpin
from widget_library.ok_cancel_buttons_widget import OkButtonWidget, CancelButtonWidget, OkSendButtonWidget
#from global_widgets.global_send_popup import SetConfirmPopup
from widget_library.line_edit_widget import LabelledLineEditWidget
from handler_library.handler import PayloadHandler
from PySide2.QtCore import Signal
from PySide2 import QtWidgets, QtGui, QtCore
class PersonalHandler(PayloadHandler): # chose QWidget over QDialog family because easier to modify
UpdatePersonalDisplay = Signal(dict)
def __init__(self, NativeUI, *args, **kwargs):
super().__init__(['PERSONAL'], *args, **kwargs)
self.NativeUI = NativeUI
self.spinDict = {}
self.buttonDict = {}
self.textDict = {}
def add_widget(self, widget, key: str):
if isinstance(widget, labelledSpin):
self.spinDict[key] = widget
if isinstance(widget, LabelledLineEditWidget):
self.textDict[key] = widget
if isinstance(widget, OkButtonWidget) or isinstance(widget, CancelButtonWidget) or isinstance(widget, OkSendButtonWidget):
self.buttonDict[key] = widget
def active_payload(self, *args, **kwargs) -> int:
"""
When the personal data is updated, extract those parameters needed for display
and emit the UpdatePersonalDisplay signal.
"""
current_data = self.get_db()
outdict = {}
for key in ["name", "patient_id", "age", "sex", "height", "weight"]:
outdict[key] = current_data[key]
self.UpdatePersonalDisplay.emit(outdict)
return 0
\ No newline at end of file
......@@ -2,11 +2,32 @@
## Unit Tests
To run the unit tests on a Raspberry Pi or VM, run the following:
To run all unit tests in the `NativeUI/tests/unittests` dir on a Raspberry Pi or VM, run the following
(adjust the full path to your NativeUI directory accordingly):
```bash
source .hev_env/bin/activate
pytest NativeUI
. .hev_env/bin/activate
export PYTHONPATH=/home/pi/hev/NativeUI
pytest NativeUI/tests/unittests
```
To run a single unit test file, set the environment as above and specify the test file:
```bash
pytest NativeUI/tests/unittests/test_hevclient.py
```
## Integration Tests
To run all integration tests, the Arduino emulator and hevserver processes first need to be running, and
then run all integrations tests in the `NativeUI/tests/integration` dir:
```bash
. .hev_env/bin/activate
export PYTHONPATH=/home/pi/hev/NativeUI
./raspberry-dataserver/ArduinoEmulator.py -f raspberry-dataserver/share/B6-20201207.dump &
./raspberry-dataserver/hevserver.py --use-dump-data &
pytest NativeUI/tests/integration
```
### Coverage
......@@ -18,7 +39,8 @@ pip install pytest-cov
pytest --cov=NativeUI NativeUI
```
## Integration Tests
## System Tests
### Template
......
"""
Create hevclient WITH a hevserver running and assert expected hevclient state and function calls.
Make sure your PYTHONPATH var is set to the full path of '/<your_hev_root_dir>/hev/NativeUI'.
"""
import os
import tempfile
import time
import pytest
import hevclient
from hevclient import HEVClient
# Overwrite the mm file for OS agnostic testing
hevclient.mmFileName = tempfile.gettempdir() + os.path.sep + "HEVCLIENT_last_Data.mmap"
def set_up():
"""pytest module setup"""
_assert_posix()
_assert_pythonpath()
def test_hev_client_state(caplog):
"""Assert HEVClient state with background processes running (hevserver/Arduino)"""
HEVClient(True)
time.sleep(1) # give enough time for het to log
# confirm message 'is the microcontroller running?' is NOT logged using err code
assert "[Errno" not in caplog.text
# TODO assert hevclient.method tests
# myhevclient.send_cmd("CMD", "blah")
def _assert_posix():
assert os.name == "posix"
def _assert_pythonpath():
pythonpath_key = "$PYTHONPATH"
pythonpath_val = os.path.expandvars(pythonpath_key)
if pythonpath_val == pythonpath_key:
pytest.fail(msg="Please set the $PYTHONPATH env var")
File mode changed from 100644 to 100755
{
"version": 182,
"timestamp": 0,
"type": "BATTERY",
"bat": 0,
"ok": 1,
"alarm": 0,
"rdy2buf": 0,
"bat85": 0,
"prob_elec": 0,
"dummy": false
}
\ No newline at end of file
{
"on_mains_power": true,
"on_battery_power": false,
"battery_percent": 0.0,
"electrical_problem": null
}
\ No newline at end of file
{
"type": "TEST",
"TEST":{
"version": 182,
"timestamp": 0,
"type": "TEST",
"attribute_1": true,
"attribute_2": 1,
"attribute_3": 0.579
}
}
......@@ -27,121 +27,122 @@ def widget():
# Test default values of databases(no set method involved)
def test_must_return_if_raises_attribute_error_when_false_db_item_is_got_from_get_db(
widget,
):
with pytest.raises(AttributeError):
widget.get_db("__false_db_item")
# Superseeded by handlers.
# def test_must_return_if_raises_attribute_error_when_false_db_item_is_got_from_get_db(
# widget,
# ):
# with pytest.raises(AttributeError):
# widget.get_db("__false_db_item")
def test_must_return_correct_db_item_from_get_db_data(widget):
assert widget.get_db("__data") == {} and widget.get_db("data") == {}
# def test_must_return_correct_db_item_from_get_db_data(widget):
# assert widget.get_db("__data") == {} and widget.get_db("data") == {}
def test_must_return_correct_db_item_from_get_db_readback(widget):
assert widget.get_db("__readback") == {} and widget.get_db("readback") == {}
# def test_must_return_correct_db_item_from_get_db_readback(widget):
# assert widget.get_db("__readback") == {} and widget.get_db("readback") == {}
def test_must_return_correct_db_item_from_get_db_cycle(widget):
assert widget.get_db("__cycle") == {} and widget.get_db("cycle") == {}
# def test_must_return_correct_db_item_from_get_db_cycle(widget):
# assert widget.get_db("__cycle") == {} and widget.get_db("cycle") == {}
def test_must_return_correct_db_item_from_get_db_battery(widget):
assert widget.get_db("__battery") == {} and widget.get_db("battery") == {}
# def test_must_return_correct_db_item_from_get_db_battery(widget):
# assert widget.get_db("__battery") == {} and widget.get_db("battery") == {}
def test_must_return_correct_db_item_from_get_db_plots(widget):
plot_history_length = 1000
plot_dict = {
"data": np.zeros((plot_history_length, 5)),
"timestamp": list(el * (-1) for el in range(plot_history_length))[::-1],
"pressure": list(0 for _ in range(plot_history_length)),
"flow": list(0 for _ in range(plot_history_length)),
"volume": list(0 for _ in range(plot_history_length)),
"pressure_axis_range": [0, 20],
"flow_axis_range": [-40, 80],
"volume_axis_range": [0, 80],
}
assert (
widget.get_db("__plots").keys() == plot_dict.keys()
and widget.get_db("plots").keys() == plot_dict.keys()
)
# def test_must_return_correct_db_item_from_get_db_plots(widget):
# plot_history_length = 1000
# plot_dict = {
# "data": np.zeros((plot_history_length, 5)),
# "timestamp": list(el * (-1) for el in range(plot_history_length))[::-1],
# "pressure": list(0 for _ in range(plot_history_length)),
# "flow": list(0 for _ in range(plot_history_length)),
# "volume": list(0 for _ in range(plot_history_length)),
# "pressure_axis_range": [0, 20],
# "flow_axis_range": [-40, 80],
# "volume_axis_range": [0, 80],
# }
# assert (
# widget.get_db("__plots").keys() == plot_dict.keys()
# and widget.get_db("plots").keys() == plot_dict.keys()
# )
def test_must_return_correct_db_item_from_get_db_alarms(widget):
assert widget.get_db("__alarms") == {} and widget.get_db("alarms") == {}
# def test_must_return_correct_db_item_from_get_db_alarms(widget):
# assert widget.get_db("__alarms") == {} and widget.get_db("alarms") == {}
def test_must_return_correct_db_item_from_get_db_targets(widget):
assert widget.get_db("__targets") == {} and widget.get_db("targets") == {}
# def test_must_return_correct_db_item_from_get_db_targets(widget):
# assert widget.get_db("__targets") == {} and widget.get_db("targets") == {}
def test_must_return_correct_db_item_from_get_db_personal(widget):
assert widget.get_db("__personal") == {} and widget.get_db("personal") == {}
# def test_must_return_correct_db_item_from_get_db_personal(widget):
# assert widget.get_db("__personal") == {} and widget.get_db("personal") == {}
# Test set methods with sample payloads
def test_must_return_0_for_set_data_db(widget):
with open("/home/pi/hev/samples/dataSample.json", "r") as f:
data_payload = json.load(f)
assert widget.__set_db("data", data_payload) == 0
# # Test set methods with sample payloads
# def test_must_return_0_for_set_data_db(widget):
# with open("/home/pi/hev/samples/dataSample.json", "r") as f:
# data_payload = json.load(f)
# assert widget.__set_db("data", data_payload) == 0
def test_must_return_0_for_set_targets_db(widget):
with open("/home/pi/hev/samples/targetSample.json", "r") as g:
target_payload = json.load(g)
assert widget.__set_db("targets", target_payload) == 0
# def test_must_return_0_for_set_targets_db(widget):
# with open("/home/pi/hev/samples/targetSample.json", "r") as g:
# target_payload = json.load(g)
# assert widget.__set_db("targets", target_payload) == 0
def test_must_return_0_for_set_readback_db(widget):
with open("/home/pi/hev/samples/readbackSample.json", "r") as f:
readback_payload = json.load(f)
assert widget.__set_db("readback", readback_payload) == 0
# def test_must_return_0_for_set_readback_db(widget):
# with open("/home/pi/hev/samples/readbackSample.json", "r") as f:
# readback_payload = json.load(f)
# assert widget.__set_db("readback", readback_payload) == 0
def test_must_return_0_for_set_cycle_db(widget):
with open(
"/home/pi/hev/NativeUI/tests/unittests/fixtures/cycleSample.json", "r"
) as f:
cycle_payload = json.load(f)
assert widget.__set_db("cycle", cycle_payload) == 0
# def test_must_return_0_for_set_cycle_db(widget):
# with open(
# "/home/pi/hev/NativeUI/tests/unittests/fixtures/cycleSample.json", "r"
# ) as f:
# cycle_payload = json.load(f)
# assert widget.__set_db("cycle", cycle_payload) == 0
def test_must_return_0_for_set_battery_db(widget):
with open("/home/pi/hev/samples/batterySample.json", "r") as f:
battery_payload = json.load(f)
assert widget.__set_db("battery", battery_payload) == 0
# def test_must_return_0_for_set_battery_db(widget):
# with open("/home/pi/hev/samples/batterySample.json", "r") as f:
# battery_payload = json.load(f)
# assert widget.__set_db("battery", battery_payload) == 0
def test_must_return_0_for_set_plots_db(widget):
with open("/home/pi/hev/samples/dataSample.json", "r") as f:
data_payload = json.load(f)
assert widget.__set_plots_db(data_payload) == 0
# def test_must_return_0_for_set_plots_db(widget):
# with open("/home/pi/hev/samples/dataSample.json", "r") as f:
# data_payload = json.load(f)
# assert widget.__set_plots_db(data_payload) == 0
def test_must_return_error_if_not_data_is_sent_as_payload_for_set_plots_db(widget):
with open("/home/pi/hev/samples/batterySample.json", "r") as f:
battery_payload = json.load(f)
with pytest.raises(KeyError):
widget.__set_plots_db(battery_payload)
# def test_must_return_error_if_not_data_is_sent_as_payload_for_set_plots_db(widget):
# with open("/home/pi/hev/samples/batterySample.json", "r") as f:
# battery_payload = json.load(f)
# with pytest.raises(KeyError):
# widget.__set_plots_db(battery_payload)
def test_must_return_0_when__update_plot_ranges_correctly(widget):
assert widget.__update_plot_ranges() == 0
# def test_must_return_0_when__update_plot_ranges_correctly(widget):
# assert widget.__update_plot_ranges() == 0
def test_must_return_0_for_set_alarms_db(widget):
with open("/home/pi/hev/samples/alarmSample.json", "r") as f:
alarm_payload = json.load(f)
assert widget.__set_db("alarms", alarm_payload) == 0
# def test_must_return_0_for_set_alarms_db(widget):
# with open("/home/pi/hev/samples/alarmSample.json", "r") as f:
# alarm_payload = json.load(f)
# assert widget.__set_db("alarms", alarm_payload) == 0
def test_must_return_0_for_set_personal_db(widget):
with open(
"/home/pi/hev/NativeUI/tests/unittests/fixtures/personalSample.json", "r"
) as f:
personal_payload = json.load(f)
assert widget.__set_db("personal", personal_payload) == 0
# def test_must_return_0_for_set_personal_db(widget):
# with open(
# "/home/pi/hev/NativeUI/tests/unittests/fixtures/personalSample.json", "r"
# ) as f:
# personal_payload = json.load(f)
# assert widget.__set_db("personal", personal_payload) == 0
# Asyncio can handle event loops, but we need to add more interaction i think
......@@ -152,6 +153,9 @@ def test_must_return_0_for_set_personal_db(widget):
def test_get_updates_data_payload(widget):
"""
Currently fails because the dataSample.json is only part of the data payload.
"""
with open("/home/pi/hev/samples/dataSample.json", "r") as f:
data_payload = json.load(f)
widget.get_updates(data_payload)
......
"""
Unit tests for the handler files.
"""
import json
import os
from unittest.mock import MagicMock, patch
from handler_library.handler import Handler
from handler_library.battery_handler import BatteryHandler
def test_handler():
"""
Tests the default handler.
Test for set_db and get_db to set the database from a given payload and compare the
db imported from get_db.
Test for if active_payload gets fired when set_db is called.
"""
# Initalise the handler and import sample test json file
handler = Handler(["TEST"])
test_json_file_path = (
os.environ["PYTHONPATH"].split(os.pathsep)[0]
+ "/tests/unittests/fixtures/testSample.json"
)
test_json = json.load(open(test_json_file_path))
# Set the database for the imported json and get the database imported
set_db_return = handler.set_db(test_json)
db = handler.get_db()
# Check if the input payload and output database are the same
if test_json["TEST"] == db:
payload_database_comparison = True
else:
payload_database_comparison = False
# Mock active_payload to return true if it is called.
handler.active_payload = MagicMock(return_value=True)
# Check whether conditions have been met to pass test
assert set_db_return == 0, "set_db does not return 0 for a valid payload"
assert (
handler.active_payload() is True
), "active_payload was not called when set_db was run."
assert (
payload_database_comparison is True
), "set_db does not set the inputted payload to the database."
def test_battery_handler():
"""
Tests the battery_handler logic by giving active_payload a sample battery payload (NativeUI/tests/unittests/fixtures/batterySample.json) with a known output status (NativeUI/tests/unittests/fixtures/battery_status_output_sample.json).
"""
# Initalise the battery handler and import sample battery json file
batt_handler = BatteryHandler()
batt_json_file_path = (
os.environ["PYTHONPATH"].split(os.pathsep)[0]
+ "/tests/unittests/fixtures/batterySample.json"
)
batt_json = json.load(open(batt_json_file_path))
# Set true/false variables
UpdateBatteryDisplay_activated = False
new_status_correctly_set = False
batt_per_1_correctly_set = False
batt_per_0_correctly_set = False
# Compute battery percent payload information
batt_per_1 = {"bat85": 1}
batt_per_0 = {"bat85": 0}
batt_handler.compute_battery_percent(batt_per_0)
# Mock the get_db function to give the sample input json
batt_handler.get_db = MagicMock(return_value=batt_json)
# Mock function to replace UpdateBatteryDisplay which checks if the function has been called and whether the output status is correct.
def mock_UpdateBatteryDisplay(new_status: dict):
nonlocal UpdateBatteryDisplay_activated
nonlocal new_status_correctly_set
# Set activated variable to true to show that this function was called
UpdateBatteryDisplay_activated = True
# Check whether new_status is the expected output
expected_status_file_path = (
os.environ["PYTHONPATH"].split(os.pathsep)[0]
+ "/tests/unittests/fixtures/battery_status_output_sample.json"
)
expected_status = json.load(open(expected_status_file_path))
if new_status == expected_status:
new_status_correctly_set = True
else:
pass
# Connect to active_payload signal
batt_handler.UpdateBatteryDisplay.connect(mock_UpdateBatteryDisplay)
# Run the battery handler logic
batt_handler.active_payload()
# Run the battery handler compute battery percent logic
if batt_handler.compute_battery_percent(batt_per_1) == 85.0:
batt_per_1_correctly_set = True
if batt_handler.compute_battery_percent(batt_per_0) == 0.0:
batt_per_0_correctly_set = True
# Check whether conditions have been met to pass test
assert (
UpdateBatteryDisplay_activated is True
), "UpdateBatteryDisplay.emit(new_status) is not called."
assert (
new_status_correctly_set is True
), "Output of new_status does not match the expected output."
assert (
batt_per_1_correctly_set is True
), "compute_battery_percent does not return 85% when bat85 is set to 1."
assert (
batt_per_0_correctly_set is True
), "compute_battery_percent does not return 0% when bat85 is set to 0."
"""
Create hevclient WITHOUT a hevserver running and assert expected hevclient state.
Make sure your PYTHONPATH var is set to the full path of '/<your_hev_root_dir>/hev/NativeUI'.
"""
import tempfile
import os
import time
import hevclient
from hevclient import HEVClient
import pytest
# Overwrite the mm file for OS agnostic testing
hevclient.mmFileName = tempfile.gettempdir() + os.path.sep + "HEVCLIENT_last_Data.mmap"
def setup_module():
"""pytest module setup"""
_assert_posix()
_assert_pythonpath()
def test_hev_client_expected_default_state(caplog):
"""Create the HEVClient in isolation without a hevserver running"""
myhevclient = HEVClient()
_hev_client_expected_state(myhevclient, caplog)
def test_hev_client_expected_log_error_on_command(caplog):
"""Create the HEVClient in isolation without a hevserver running"""
myhevclient = HEVClient(False)
myhevclient.send_cmd("CMD", "fake")
_hev_client_expected_state(myhevclient, caplog)
def _hev_client_expected_state(myhevclient: HEVClient, caplog):
assert myhevclient.get_values() is None # probably should return empty dict
assert len(myhevclient.get_alarms()) == 0
assert myhevclient.get_updates(None) is None
assert myhevclient.get_cycle() is None
assert myhevclient.get_logmsg() is None
time.sleep(1) # wait for the async log to be written
for record in caplog.records:
assert record.levelname != "CRITICAL"
assert (
"[Errno" in caplog.text
) # confirm message 'is the microcontroller running?' is logged
# Can't specify an err code as these are different across devices
def _assert_posix():
assert os.name == "posix"
def _assert_pythonpath():
pythonpath_key = "$PYTHONPATH"
pythonpath_val = os.path.expandvars(pythonpath_key)
if pythonpath_val == pythonpath_key:
pytest.fail(msg="Please set the $PYTHONPATH env var")
This diff is collapsed.
This diff is collapsed.
......@@ -40,15 +40,10 @@ class BatteryDisplayWidget(QtWidgets.QWidget):
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
self.battery_power = False
self.mains_power = False
self.battery_percent = 0
self.electrical_problem = False
self.status = {}
self.set_default_status()
self.update_value(self.status)
self.update_status(self.status)
def set_default_status(self) -> dict:
"""
......@@ -66,61 +61,18 @@ class BatteryDisplayWidget(QtWidgets.QWidget):
return self.status
@QtCore.Slot(dict)
def update_value(self, dict):
battery_data = self.NativeUI.get_db("battery")
# Update status
try:
self.status["on_mains_power"] = bool(battery_data["ok"])
except KeyError:
logging.debug("Keyerror in battery payload: 'ok'")
try:
self.status["on_battery_power"] = bool(battery_data["bat"])
except KeyError:
logging.debug("Keyerror in battery payload: 'bat'")
try:
self.status["battery_percent"] = self.compute_battery_percent(battery_data)
except KeyError:
logging.debug("Keyerror in battery payload: 'bat85'")
try:
if bool(battery_data["prob_elec"]):
self.status["electrical_problem"] = "ERROR ELEC."
else:
self.status["electrical_problem"] = None
except KeyError:
logging.debug("Keyerror in battery payload: 'prob_elec'")
# Sanity checks
if self.status["on_mains_power"] == self.status["on_battery_power"]:
# If there is conflicting information w.r.t. power source, report a problem
self.status["on_mains_power"] = False
self.status["on_battery_power"] = False
self.status["electrical_problem"] = "ERROR ELEC."
def update_status(self, input_status: dict):
"""
TODO: docstring
"""
self.status = input_status
# Update widgets with new status
for widget in self.widgets:
widget.update_value(self.status)
return 0
def compute_battery_percent(self, battery_data: dict) -> float:
"""
Determine the current battery percentage from the information in battery_data.
As of 17/03/21 battery payloads only contain enough information to
determine if the battery is above or below 85% battery life.
Unless provided with specific information to the contrary, assume that the
battery is on 0% so that we should never overestimate how much remains.
"""
if battery_data["bat85"] == 1:
return 85.0
elif battery_data["bat85"] == 0:
return 0.0
return 0.0
def set_size(self, x: int, y: int) -> int:
def set_size(self, x: int, y: int, spacing=None) -> int:
"""
Set the size of the battery display widget. Due to the way that the text_display
needs to resize itself, both the x and y sizes must be specified.
......@@ -143,8 +95,7 @@ class BatteryDisplayWidget(QtWidgets.QWidget):
class BatteryText(QtWidgets.QLabel):
"""
"""
""""""
def __init__(self, NativeUI, *args, **kwargs):
super().__init__("", *args, **kwargs)
......
from global_widgets.global_spinbox import labelledSpin
from widget_library.ok_cancel_buttons_widget import OkButtonWidget, CancelButtonWidget, OkSendButtonWidget
from PySide2 import QtWidgets, QtGui, QtCore
from handler_library.handler import PayloadHandler
import logging
import json
class ExpertHandler(PayloadHandler): # chose QWidget over QDialog family because easier to modify
UpdateExpert = QtCore.Signal(dict)
OpenPopup = QtCore.Signal(PayloadHandler,list)
def __init__(self, NativeUI, *args, **kwargs):
super().__init__(['READBACK'],*args, **kwargs)
self.NativeUI = NativeUI
self.spinDict = {}
self.buttonDict = {}
self.manuallyUpdated = False
self.commandList = []
with open("NativeUI/configs/expert_config.json") as json_file:
controlDict = json.load(json_file)
self.relevantKeys = [list[2] for key in controlDict for list in controlDict[key]]
def add_widget(self, widget, key: str):
if isinstance(widget, labelledSpin):
self.spinDict[key] = widget
if isinstance(widget, OkButtonWidget) or isinstance(widget, CancelButtonWidget) or isinstance(widget,OkSendButtonWidget):
self.buttonDict[key] = widget
def active_payload(self, *args) -> int:
readback_data = self.get_db()
outdict = {}
for key in self.relevantKeys:
try:
outdict[key] = readback_data[key]
except KeyError:
logging.debug("Invalid key %s in measurement database", key)
self.UpdateExpert.emit(outdict)
return 0
def handle_okbutton_click(self, key):
message, command = [], []
for widget in self.spinDict:
if self.spinDict[widget].manuallyUpdated:
setVal = self.spinDict[widget].get_value()
setVal = round(setVal, widget.decPlaces)
message.append("set" + widget + " to " + str(setVal))
command.append(
[
self.spinDict[widget].cmd_type,
self.spinDict[widget].cmd_code,
setVal,
]
)
self.commandList = command
if 'send' in key:
self.sendCommands()
else:
self.OpenPopup.emit(self,message)
def sendCommands(self):
if self.commandList == []:
a=1
else:
for command in self.commandList:
self.NativeUI.q_send_cmd(*command)
self.commandSent()
return 0
def commandSent(self):
self.commandList = []
for widget in self.spinDict:
self.spinDict[widget].manuallyUpdated = False
self.refresh_button_colour()
def handle_manual_change(self, changed_spin_key):
self.refresh_button_colour()
def refresh_button_colour(self):
self.manuallyUpdated = False
for spin in self.spinDict:
self.manuallyUpdated = self.manuallyUpdated or self.spinDict[spin].manuallyUpdated
for button in self.buttonDict:
self.buttonDict[button].setColour(str(int(self.manuallyUpdated)))
This diff is collapsed.
......@@ -9,10 +9,10 @@ class SignallingLineEditWidget(QtWidgets.QLineEdit):
super().__init__()
self.installEventFilter(self)
self.popUp = TypeValuePopup(NativeUI)
self.popUp.lineEdit.setValidator(None) # nsure it accepts text
self.popUp.okButton.clicked.connect(self.okButtonPressed)
self.popUp.cancelButton.clicked.connect(self.cancelButtonPressed)
#self.popUp = TypeValuePopup(NativeUI)#,'text edit',0,1,2,3,4)
#self.popUp.lineEdit.setValidator(None) # nsure it accepts text
#self.popUp.okButton.clicked.connect(self.okButtonPressed)
#self.popUp.cancelButton.clicked.connect(self.cancelButtonPressed)
def okButtonPressed(self):
val = self.popUp.lineEdit.text()
......@@ -21,7 +21,7 @@ class SignallingLineEditWidget(QtWidgets.QLineEdit):
self.manualChanged.emit()
def cancelButtonPressed(self):
self.popUp.lineEdit.setText(self.popUp.lineEdit.saveVal)
self.popUp.lineEdit.setText(self.popUp.lineEditp.saveVal)
self.popUp.close()
def eventFilter(self, source, event):
......@@ -44,8 +44,8 @@ class LabelledLineEditWidget(QtWidgets.QWidget):
self.label, self.units, self.tag, self.cmd_type, self.cmd_code, self.min, self.max, self.step, self.decPlaces = (
infoArray
)
elif len(infoArray) == 5:
self.label, self.units, self.tag, self.cmd_type, self.cmd_code = infoArray
elif len(infoArray) == 6:
self.label, self.units, self.tag, self.cmd_type, self.cmd_code, self.initText = infoArray
elif len(infoArray) == 3:
self.label, self.units, self.tag = infoArray
self.manuallyUpdated = False
......@@ -60,7 +60,7 @@ class LabelledLineEditWidget(QtWidgets.QWidget):
widgetList.append(self.nameLabel)
self.simpleSpin = SignallingLineEditWidget(NativeUI)
self.simpleSpin.setText("")
self.simpleSpin.setText(self.initText)
self.simpleSpin.setStyleSheet(
"""QDoubleSpinBox{ width:100px; font:16pt}
QDoubleSpinBox[bgColour="0"]{background-color:white; }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -202,7 +202,7 @@ class ClientPlots(QtWidgets.QMainWindow):
client = HEVClient(polling=False) # just use hevclient for requests
await asyncio.sleep(2)
# trigger an alarm
await client.send_request("CMD", cmdtype="SET_THRESHOLD_MIN", cmd="APNEA",param=0)
await client._send_request("CMD", cmdtype="SET_THRESHOLD_MIN", cmd="APNEA", param=0)
while True:
await asyncio.sleep(60)
except Exception as e:
......
......@@ -29,7 +29,8 @@ import logging
import binascii
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s (%(filename)s line %(lineno)d: %(module)s.%(funcName)s)",
)
# VERSIONING
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment