Commit 7b2e6e96 authored by Karol Hennessy's avatar Karol Hennessy

Merge branch 'test_UI_new_state_machines_merge' into 'master'

Test ui new state machines merge

See merge request !13
parents ff1f0719 05db8768
Pipeline #1707 canceled with stages
......@@ -11,3 +11,161 @@ __pycache__
*.sqlite
*.csv
raspberry-dataserver/foo
env/
.cache
.pylintrc
.pre-commit-config.yaml
.env
ansible/playbooks/hosts
.idea
scratch_*
*/*/startup_config.json
NativeUI/configs/startup_config.json
# python virtual env created using ansible
.hev_env
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
pytestdebug.log
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
doc/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
pythonenv*
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# profiling data
.prof
# End of https://www.toptal.com/developers/gitignore/api/python
stages:
- build
- test
build:raspi4-qmake:
before_script:
- pwd
- echo 'test'
- groupadd pi
- useradd -u 1000 -g pi -m -p raspberry pi
- usermod -aG sudo pi
- mkdir -p /home/pi/Downloads
- apt -y autoremove
- apt -y update
- apt -y upgrade
- apt -y install python3.7 software-properties-common python3-pip git-all raspi-config
- python3 --version
- pip3 install ansible
- ansible --version
# install_packages:
# stage: install
# script:
# - apt -y autoremove
# - apt -y update
# - apt -y upgrade
# - apt -y install python3.7 software-properties-common python3-pip git-all raspi-config
# - python3 --version
# - pip3 install ansible
# - ansible --version
ui_installation:
stage: build
image: etalian/qt-raspi4
before_script:
- mkdir -p "${CI_PROJECT_DIR}/binaries"
script:
- cd "${CI_PROJECT_DIR}/hev-display"
- /raspi/qt5/bin/qmake
- make
- mkdir /tmp/${CI_PROJECT_NAME} && cd "$_"
- cmake --config Release
-DCMAKE_TOOLCHAIN_FILE=/raspi/gcc-linaro-arm-linux-gnueabihf-raspbian-x64.cmake
-DCPACK_PACKAGE_SUFFIX=-pi4 -DCPACK_SYSTEM_NAME=raspbian10 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=armhf
"${CI_PROJECT_DIR}/hev-display"
- make
- cpack -G DEB && cp -v *.deb "${CI_PROJECT_DIR}/binaries"
artifacts:
name: "${CI_JOB_NAME}-${CI_COMMIT_REF_SLUG}~git${CI_COMMIT_SHORT_SHA}"
paths:
- hev-display
- binaries/
- ls -a
- pwd
- cd /home/pi/hev
- ./setup.sh CI
ui_test:
stage: test
script:
- echo "Tests will be here."
\ No newline at end of file
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
exclude: ^[\S]*{{cookiecutter[\S]*
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.1.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-json
# - id: pretty-format-json
- id: requirements-txt-fixer
- repo: https://github.com/psf/black
rev: 19.3b0
hooks:
- id: black
language_version: python3.7
# - repo: https://github.com/timothycrosley/isort
# rev: 5.0.8
# hooks:
# - id: isort
- repo: https://github.com/pycqa/pylint
rev: pylint-2.6.0
hooks:
- id: pylint
args:
- --rcfile=pylint.cfg
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
#!/usr/bin/env python3
"""
alarm_list.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
import os
from PySide2 import QtCore, QtGui, QtWidgets
from datetime import datetime
class AlarmList(QtWidgets.QListWidget):
def __init__(self, NativeUI, *args, **kwargs):
super(AlarmList, self).__init__(*args, **kwargs)
self.labelList = []
self.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)
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")
self.solidBell = QtGui.QIcon(iconpath_bell)
self.regularBell = QtGui.QIcon(iconpath_bellReg)
newItem = QtWidgets.QListWidgetItem(" ")
self.addItem(newItem)
def acknowledge_all(self):
for x in range(self.count() - 1):
self.item(x).setText("acknowledgedAlarm")
self.item(x).setIcon(self.regularBell)
def addAlarm(self, abstractAlarm):
timestamp = str(abstractAlarm.startTime)[:-3]
newItem = QtWidgets.QListWidgetItem(
self.solidBell,
timestamp
+ ": "
+ abstractAlarm.alarmPayload["alarm_type"]
+ " - "
+ abstractAlarm.alarmPayload["alarm_code"],
)
self.insertItem(0, newItem) # add to the top
# self.labelList
def removeAlarm(self, abstractAlarm):
for x in range(self.count() - 1):
if abstractAlarm.alarmPayload["alarm_code"] in self.item(x).text():
self.takeItem(x)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widg = alarmList()
widg.show()
sys.exit(app.exec_())
#!/usr/bin/env python3
"""
alarm_popup.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 os
from PySide2 import QtCore, QtGui, QtWidgets
from datetime import datetime
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)
popup_height = int(NativeUI.alarm_popup_width / 10.0)
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(popup_height)
iconLabel.setPixmap(pixmap)
self.layout.addWidget(iconLabel)
self.textLabel = QtWidgets.QLabel()
alarmLevel = self.alarmPayload["alarm_type"] # .replace('PRIORITY_', '')
self.textLabel.setText(
self.alarmPayload["alarm_code"] + " - (" + alarmLevel + ")"
)
self.textLabel.setFixedWidth(NativeUI.alarm_popup_width)
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(popup_height)
self.setLayout(self.layout)
if self.alarmPayload["alarm_type"] == "PRIORITY_HIGH":
self.setStyleSheet("background-color:red;")
elif self.alarmPayload["alarm_type"] == "PRIORITY_MEDIUM":
self.setStyleSheet("background-color:orange;")
self.setFixedSize(NativeUI.alarm_popup_width + popup_height, popup_height)
# 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.widgets.page_buttons.alarms_button.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
# for alarm in self.ongoingAlarms:
# if self.alarmPayload["alarm_code"] == alarm["alarm_code"]:
# return
# self.alarmCarrier.alarmDict.pop(self.alarmPayload["alarm_code"])
# self.setParent(None) # delete self
# return 0
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)
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)
self.layout.setMargin(0)
self.setLayout(self.layout)
self.location_on_window()
self.setWindowFlags(
QtCore.Qt.FramelessWindowHint
| QtCore.Qt.Dialog
| QtCore.Qt.WindowStaysOnTopHint
) # no window title
self.shadow = QtWidgets.QGraphicsDropShadowEffect()
self.shadow.setBlurRadius(20)
self.shadow.setXOffset(10)
self.shadow.setYOffset(10)
self.timer = QtCore.QTimer()
self.timer.setInterval(100) # just faster than 60Hz
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())):
self.layout.itemAt(i).widget().setParent(None)
self.adjustSize()
self.setLayout(self.layout)
self.alarmDict = {}
return 0
def addAlarm(self, abstractAlarm):
"""Creates a new alarmWidget and adds it to the container"""
self.alarmDict[abstractAlarm.alarmPayload["alarm_code"]] = AlarmWidget(
self.NativeUI, abstractAlarm, self
)
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()
def location_on_window(self):
"""Position the popup as defined here"""
screen = QtWidgets.QDesktopWidget().screenGeometry()
x = screen.width() - screen.width() / 2
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)
popup_height = int(NativeUI.alarm_popup_width / 10.0)
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(popup_height)
iconLabel.setPixmap(pixmap)
self.layout.addWidget(iconLabel)
self.textLabel = QtWidgets.QLabel()
self.textLabel.setText("1 More Alarms")
self.textLabel.setFixedWidth(NativeUI.alarm_popup_width)
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(popup_height)
self.setLayout(self.layout)
self.setStyleSheet("background-color:red;")
# self.priority = "PRIORITY_LOW"
self.installEventFilter(self)
self.setFixedSize(NativeUI.alarm_popup_width + popup_height, popup_height)
def update_text(self, num):
self.textLabel.setText(str(num) + " More Alarms")
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
self.NativeUI.widgets.page_buttons.alarms_button.click()
return False
def get_priority(self):
return self.alarmPayload["alarm_type"]
#!/usr/bin/env python3
"""
alarm_list.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
import os
from PySide2 import QtCore, QtGui, QtWidgets
from datetime import datetime
class AlarmTable(QtWidgets.QTableWidget):
"""Table containing all of the alarms since power up are contained. Easily sorted"""
def __init__(self, NativeUI, *args, **kwargs):
super(AlarmTable, self).__init__(*args, **kwargs)
self.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)
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.payloadKeys = ["alarm_type", "alarm_code"]
self.resizeColumnsToContents()
self.alarmCellDict = {}
self.timer = QtCore.QTimer()
self.timer.setInterval(100)
# 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"],
# )
# self.insertItem(0, newItem) # add to the top
# def removeAlarm(self, abstractAlarm):
# for x in range(self.count() - 1):
# if abstractAlarm.alarmPayload["alarm_code"] in self.item(x).text():
# self.takeItem(x)
def addAlarmRow(self, abstractAlarm):
"""Add a new row 1 cell at a time. Goes through alarm payload to fill information"""
self.setSortingEnabled(False)
self.setRowCount(self.nrows + 1)
newItem = QtWidgets.QTableWidgetItem(str(abstractAlarm.startTime)[:-3])
self.setItem(self.nrows, 0, newItem)
newItem = QtWidgets.QTableWidgetItem(abstractAlarm.alarmPayload["alarm_type"])
self.setItem(self.nrows, 1, newItem)
newItem = QtWidgets.QTableWidgetItem(abstractAlarm.alarmPayload["alarm_code"])
self.setItem(self.nrows, 2, newItem)
newItem = QtWidgets.QTableWidgetItem(" ")
self.alarmCellDict[self.nrows] = newItem
self.setItem(self.nrows, 3, self.alarmCellDict[self.nrows])
# abstractAlarm.alarmExpired.connect(lambda i = self.alarmCellDict[self.nrows], j = abstractAlarm: self.update_duration(i,j))
self.timer.timeout.connect(
lambda i=self.alarmCellDict[self.nrows], j=abstractAlarm: self.update_duration(
i, j
)
)
#if self.nrows == 1:
self.resizeColumnsToContents()
self.nrows = self.nrows + 1
self.setSortingEnabled(True)
def update_duration(self, cellItem, abstractAlarm):
cellItem.setText(str(abstractAlarm.duration))
#!/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 alarm_widgets.alarm_popup import alarmPopup, abstractAlarm
from alarm_widgets.alarm_table import alarmTable
from PySide2 import QtCore, QtGui, QtWidgets
class TabAlarmTable(QtWidgets.QWidget):
def __init__(self, NativeUI, *args, **kwargs):
super(TabAlarmTable, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
self.table = alarmTable(NativeUI)
vlayout = QtWidgets.QVBoxLayout()
vlayout.addWidget(self.table)
self.acknowledgeButton = QtWidgets.QPushButton('table button')
#self.acknowledgeButton.pressed.connect(self.acknowledge_pressed)
vlayout.addWidget(self.acknowledgeButton)
self.setLayout(vlayout)
\ No newline at end of file
#!/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 alarm_widgets.alarm_popup import alarmPopup, abstractAlarm
from alarm_widgets.alarm_list import alarmList
from PySide2 import QtCore, QtGui, QtWidgets
class TabAlarm(QtWidgets.QWidget):
def __init__(self, NativeUI, *args, **kwargs):
super(TabAlarm, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
# self.alarmDict = {}
self.popup = alarmPopup(NativeUI, self)
self.popup.show()
self.list = alarmList(NativeUI)
vlayout = QtWidgets.QVBoxLayout()
vlayout.addWidget(self.list)
self.acknowledgeButton = QtWidgets.QPushButton()
self.acknowledgeButton.pressed.connect(self.acknowledge_pressed)
vlayout.addWidget(self.acknowledgeButton)
self.setLayout(vlayout)
self.alarmDict = {}
# fdd
# self.timer = QtCore.QTimer()
# self.timer.setInterval(160)
# self.timer.timeout.connect(self.updateAlarms)
# self.timer.start()
def acknowledge_pressed(self):
self.popup.clearAlarms()
self.list.acknowledge_all()
def update_alarms(self):
newAlarmPayload = self.NativeUI.get_db("alarms")
if newAlarmPayload == {}:
return
if newAlarmPayload["alarm_code"] in self.alarmDict:
a = 1
self.alarmDict[newAlarmPayload["alarm_code"]].resetTimer()
self.alarmDict[newAlarmPayload["alarm_code"]].calculateDuration()
else:
newAbstractAlarm = abstractAlarm(self.NativeUI, newAlarmPayload)
self.alarmDict[newAlarmPayload["alarm_code"]] = newAbstractAlarm
newAbstractAlarm.alarmExpired.connect(
lambda i=newAbstractAlarm: self.handleAlarmExpiry(i)
)
self.popup.addAlarm(newAbstractAlarm)
self.list.addAlarm(newAbstractAlarm)
self.NativeUI.widgets.alarm_table_tab.table.addAlarmRow(newAbstractAlarm)
def handleAlarmExpiry(self, abstractAlarm):
abstractAlarm.freezeTimer()
self.popup.removeAlarm(abstractAlarm)
self.list.removeAlarm(abstractAlarm)
self.alarmDict.pop(abstractAlarm.alarmPayload["alarm_code"])
abstractAlarm.recordFinishTime()
#!/usr/bin/env python3
"""
tab_clinical.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"
from PySide2 import QtWidgets, QtGui, QtCore
from global_widgets.template_set_values import TemplateSetValues
class TabClinical(TemplateSetValues):
def __init__(self, *args, **kwargs):
super(TabClinical, self).__init__(*args, **kwargs)
clinicalList = [
["APNEA", "ms", ""],
["Check Pressure Patient", "ms", ""],
["High FIO2", "ms", ""],
["High Pressure_Low", " ", ""],
["High Respiratory Rate", " ", ""],
["High VTE", " ", ""],
["Low VTE", " ", ""],
["High VTI", " ", ""],
["Low VTI", " ", ""],
["Low FIO2", " ", ""],
["Occlusion_Low", " ", ""],
["High PEEP", " ", ""],
["Low PEEP", " ", ""],
]
self.addSpinDblCol(clinicalList)
self.addButtons()
self.finaliseLayout()
{
"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, 1, 0],["Percentage O2", "", "fiO2_percent", "SET_TARGET_CURRENT", "FIO2_PERCENT", 0, 100, 51, 1, 0],["FIO2", "%", "HIGH_FIO2", "SET_THRESHOLD_MAX", "HIGH_FIO2", 0, 10, 5, 1, 0]],
[["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
{
"page_background": [30, 30, 30],
"page_foreground": [200, 200, 200],
"button_background_enabled":[50, 50, 50],
"button_background_disabled":[100, 100, 100],
"button_foreground_enabled":[200, 200, 200],
"button_foreground_disabled":[30, 30, 30],
"button_background_highlight":[30,93,248],
"button_foreground_highlight":[200,200,200],
"label_background":[0, 0, 0],
"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],
"plot_pressure":[0, 114, 178],
"plot_volume":[0, 158, 115],
"plot_flow":[240, 228, 66],
"plot_pressure_flow":[230, 159, 0],
"plot_flow_volume":[204, 121, 167],
"plot_volume_pressure":[86, 180, 233],
"highligh":[30,93,248],
"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, 51, 0.1, 1]],
"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", "PEEP" ]
}
\ 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": 1622034526, "cmd_code": "calib_rate"}, "leak_test": {"label": "Leak Test", "last_performed": 1622034527, "cmd_code": "leak_test"}, "maintenance": {"label": "maintenance", "last_performed": 1622034528, "cmd_code": "main_tenance"}}
\ No newline at end of file
{
"language_name": "English",
"start_button": "START",
"stop_button": "STOP",
"standby_button": "STANDBY",
"PC/AC": "PC/AC",
"PC/AC-PRVC": "PC/AC-PRVC",
"PC-PSV": "PC-PSV",
"CPAP": "CPAP",
"plot_axis_label_pressure": "Pressure [cmH<sub>2</sub>O]",
"plot_axis_label_flow": "Flow [L/min]",
"plot_axis_label_volume": "Volume [mL]",
"plot_axis_label_time": "Time [s]",
"plot_line_label_pressure": "Airway Pressure",
"plot_line_label_flow": "Flow",
"plot_line_label_volume": "Volume",
"plot_line_label_pressure_flow": "Airway Pressure - Flow",
"plot_line_label_flow_volume": "Flow - Volume",
"plot_line_label_volume_pressure": "Volume - Airway Pressure",
"layout_label_measurements": "Measurements",
"button_label_main_normal": "Normal",
"button_label_main_detailed": "Detailed",
"button_label_alarms_list": "List of Alarms",
"button_label_alarms_table": "Alarm Table",
"button_label_alarms_clinical": "Clinical Limits",
"button_label_settings_expert": "Expert",
"button_label_settings_charts": "Charts",
"button_label_settings_info": "System Info",
"button_label_modes_mode": "Mode Settings",
"button_label_modes_personal": "Personal Settings",
"button_label_modes_summary": "Summary",
"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> [%]",
"measurement_label_exhaled_tidal_volume": "VTE [mL]",
"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>MEAN</sub> [cmH<sub>2</sub>O]",
"measurement_label_inhaled_tidal_volume": "VTI [mL]",
"measurement_label_inhaled_minute_volume": "MVI [L/min]",
"measurement_label_peak_inspiratory_pressure": "P<sub>PEAK</sub> [cmH<sub>2</sub>O]",
"spin_box_label_Inhale_Pressure": "Inhale Pressure",
"spin_box_label_Respiratory_Rate": "Respiratory Rate",
"spin_box_label_Inhale_Time": "Inhale Time",
"spin_box_label_IE_Ratio": "IE Ratio",
"spin_box_label_Percentage_O2": "Percentage O2",
"personal_tab_name": "Name",
"personal_tab_patientid": "Patient ID",
"personal_tab_age": "Age",
"personal_tab_sex": "Sex",
"personal_tab_weight": "Weight",
"personal_tab_height": "Height"
}
{
"language_name": "Portugues",
"start_button": "INICIAR",
"stop_button": "PARAR",
"standby_button": "ESPERAR",
"PC/AC": "PC/AC",
"PC/AC-PRVC": "PC/AC-PRVC",
"PC-PSV": "PC-PSV",
"CPAP": "CPAP",
"plot_axis_label_pressure": "Pressao [cmH<sub>2</sub>O]",
"plot_axis_label_flow": "Fluxo [L/min]",
"plot_axis_label_volume": "Volume [mL]",
"plot_axis_label_time": "Tempo [s]",
"plot_line_label_pressure": "Pressao de Ar",
"plot_line_label_flow": "Fluxo",
"plot_line_label_volume": "Volume",
"plot_line_label_pressure_flow": "Pressao de Ar - Fluxo",
"plot_line_label_flow_volume": "Fluxo - Volume",
"plot_line_label_volume_pressure": "Volume - Pressao de Ar",
"layout_label_measurements": "Medicoes",
"button_label_main_normal": "Normal",
"button_label_main_detailed": "Detalhado",
"button_label_alarms_list": "-",
"button_label_alarms_table": "-",
"button_label_alarms_clinical": "-",
"button_label_settings_expert": "-",
"button_label_settings_charts": "-",
"button_label_settings_info": "-",
"button_label_modes_mode": "-",
"button_label_modes_personal": "-",
"button_label_modes_summary": "-",
"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_exhaled_tidal_volume": "VOL<sub>EXAL</sub> [mL]",
"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]",
"measurement_label_inhaled_tidal_volume": "VTI [mL]",
"measurement_label_inhaled_minute_volume": "MVI [L/min]",
"measurement_label_peak_inspiratory_pressure": "P<sub>PICO</sub> [cmH<sub>2</sub>O]",
"spin_box_label_Inhale_Pressure": "Pressao inspiracao",
"spin_box_label_Respiratory_Rate": "Frequencia Respiratoria",
"spin_box_label_Inhale_Time": "Tempo Inspiracao",
"spin_box_label_IE_Ratio": "Razao I/E",
"spin_box_label_Percentage_O2": "Porcetagem O<sub>2</sub>",
"personal_tab_name": "Nome",
"personal_tab_patientid": "ID do paciente",
"personal_tab_age": "Idade",
"personal_tab_sex": "Sexo",
"personal_tab_weight": "Peso",
"personal_tab_height": "Altura"
}
#!/usr/bin/env python3
"""
global_select_button.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"
from PySide2 import QtWidgets, QtGui, QtCore
class selectorButton(QtWidgets.QPushButton):
"""A button styled with two colour options, to use for tab selection"""
def __init__(self, NativeUI, *args, **kwargs):
super(selectorButton, self).__init__(*args, **kwargs)
self.setFont(NativeUI.text_font)
style = (
"QPushButton[selected='0']{"
" color: " + NativeUI.colors["page_foreground"].name() + ";"
" background-color: "
+ NativeUI.colors["button_background_enabled"].name()
+ ";"
" border:none"
"}"
"QPushButton[selected='1']{"
" color: " + NativeUI.colors["page_background"].name() + ";"
" background-color:"
+ NativeUI.colors["button_foreground_disabled"].name()
+ ";"
" border:none"
"}"
)
self.setStyleSheet(style)
self.setProperty("selected", "0")
#!/usr/bin/env python3
"""
global_send_popup.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"
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
import os
class SetConfirmPopup(QtWidgets.QDialog):
"""Popup called when user wants to send new values to microcontroller.
This popup shows changes and asks for confirmation"""
# a signal for each handler, so the UI knows which values were updated
ExpertSend = QtCore.Signal()
ModeSend = QtCore.Signal()
PersonalSend = QtCore.Signal()
ClinicalSend = QtCore.Signal()
def __init__(self, NativeUI, *args, **kwargs):
super().__init__(*args, **kwargs)
self.NativeUI = NativeUI
self.handler = None
# list widget displays the changes to be sent to MCU in human readable way
self.listWidget = QtWidgets.QListWidget()
self.listWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
buttonHLayout = QtWidgets.QHBoxLayout()
self.okButton = OkButtonWidget(self.NativeUI)
self.okButton.setEnabled(True)
self.okButton.pressed.connect(self.ok_button_pressed)
buttonHLayout.addWidget(self.okButton)
self.cancelButton = CancelButtonWidget(self.NativeUI)
self.cancelButton.setEnabled(True)
buttonHLayout.addWidget(self.cancelButton)
vlayout = QtWidgets.QVBoxLayout()
vlayout.addWidget(self.listWidget)
vlayout.addLayout(buttonHLayout)
self.setLayout(vlayout)
def populatePopup(self, handlerWidget, messageList):
"""One popup is used for all the handlers. It is populated when called by a particular handler"""
self.handler = handlerWidget
self.listWidget.clear()
if messageList == []:
messageList = ["no values were set"]
for item in messageList:
listItem = QtWidgets.QListWidgetItem(item)
listItem.setFlags(QtCore.Qt.NoItemFlags)
self.listWidget.addItem(listItem)
# adjust size according to list contents
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()
return 0
def ok_button_pressed(self):
"""Emit signal to connect with handler corresponding to editted values."""
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"""
# print("CANCEL BUTTON PRESSED")
# # self.close()
# return 0
class confirmWidget(QtWidgets.QWidget):
"""A widget displaying an individual command confirmation from the MCU. Is contained in confirmPopup"""
def __init__(self, NativeUI, confirmMessage, *args, **kwargs):
super(confirmWidget, self).__init__(*args, **kwargs)
self.hlayout = QtWidgets.QHBoxLayout()
self.hlayout.setSpacing(0)
self.hlayout.setMargin(0)
self.confirmMessage = confirmMessage
iconLabel = QtWidgets.QLabel()
iconpath_check = os.path.join(NativeUI.iconpath, "exclamation-circle-solid.png")
pixmap = QtGui.QPixmap(iconpath_check).scaledToHeight(40)
iconLabel.setPixmap(pixmap)
self.hlayout.addWidget(iconLabel)
textLabel = QtWidgets.QLabel()
textLabel.setText(self.confirmMessage)
textLabel.setFixedHeight(40)
textLabel.setFixedWidth(400)
textLabel.setAlignment(QtCore.Qt.AlignCenter)
self.hlayout.addWidget(textLabel)
self.setLayout(self.hlayout)
self.setFixedHeight(50)
# create timer to handle timeout
self.timer = QtCore.QTimer()
self.timer.setInterval(10000)
self.timer.timeout.connect(self.confirmTimeout)
self.timer.start()
def confirmTimeout(self):
"""Widget should expire after a defined time"""
self.parent().confirmDict.pop(
self.confirmMessage.replace("/", "_").replace("-", "_") # - and / are not used in dictionary keys
)
self.setParent(None) # delete self
class confirmPopup(QtWidgets.QDialog):
"""Popup when a command is confirmed by microcontroller.
This popup is a frame containing a confirmWidget object for
each successful command."""
def __init__(self, NativeUI, *args, **kwargs):
super(confirmPopup, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
self.confirmDict = {}
self.vlayout = QtWidgets.QVBoxLayout()
self.vlayout.setSpacing(0)
self.vlayout.setMargin(0)
self.setLayout(self.vlayout)
self.setStyleSheet("background-color:green;")
self.location_on_window()
self.setWindowFlags(
QtCore.Qt.FramelessWindowHint
| QtCore.Qt.Dialog
| QtCore.Qt.WindowStaysOnTopHint
) # no window title
self.timer = QtCore.QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.adjustSize) # container needs to adjust to a new number of confirmWidgets
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(
self.NativeUI, confirmMessage
) # record in dictionary so it can be accessed and deleted
self.vlayout.addWidget(self.confirmDict[confirmMessage])
return 0
def location_on_window(self):
"""Places confirmWidgets at the top center of the screen"""
screen = QtWidgets.QDesktopWidget().screenGeometry()
# widget = self.geometry()
x = screen.width() - screen.width() / 2
y = 0 # screen.height() - widget.height()
self.move(x, y)
return 0
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/env python3
"""
tab_hold_buttons.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 logging
from PySide2 import QtCore, QtGui, QtWidgets
from widget_library.ok_cancel_buttons_widget import OkButtonWidget, CancelButtonWidget
# from global_widgets.global_ok_cancel_buttons import okButton, cancelButton
import time
class timerConfirmPopup(QtWidgets.QWidget):
"""Popup when start stop standby buttons are pressed. Counts a certain amount of time before switching on,
showing a progress bar meanwhile"""
def __init__(self, NativeUI, *args, **kwargs):
super(timerConfirmPopup, self).__init__(*args, **kwargs)
self.setAttribute(
QtCore.Qt.WA_ShowWithoutActivating
) # keep the main page activated to maintain button hold
self.setWindowFlags(
QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint
) # ensures focus is not stolen by alarm or confirmation
self.setStyleSheet(
"background-color: "
+ NativeUI.colors["button_background_enabled"].name()
+ ";"
"color: " + NativeUI.colors["button_foreground_disabled"].name() + ";"
"border:none"
)
self.stack = QtWidgets.QStackedWidget()
# Define progress bar
vlayout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel("progressing")
vlayout.addWidget(self.label)
self.progressBar = QtWidgets.QProgressBar()
self.progressBar.setMaximum(3000)
self.progressBar.setFormat("")
vlayout.addWidget(self.progressBar)
self.progressWidg = QtWidgets.QWidget()
self.progressWidg.setLayout(vlayout)
self.stack.addWidget(self.progressWidg)
# Define confirmation message and buttons
self.completeLayout = QtWidgets.QVBoxLayout()
self.completeLabel = QtWidgets.QLabel("confirm it")
buttonLayout = QtWidgets.QHBoxLayout()
self.okButton = OkButtonWidget(NativeUI)
buttonLayout.addWidget(self.okButton)
self.cancelButton = CancelButtonWidget(NativeUI)
self.okButton.setEnabled(True)
self.cancelButton.setEnabled(True)
buttonLayout.addWidget(self.cancelButton)
self.completeLayout.addWidget(self.completeLabel)
self.completeLayout.addLayout(buttonLayout)
self.completeWidg = QtWidgets.QWidget()
self.completeWidg.setLayout(self.completeLayout)
self.stack.addWidget(self.completeWidg)
# set layout
stackLayout = QtWidgets.QVBoxLayout()
stackLayout.addWidget(self.stack)
self.setLayout(stackLayout)
# place popup in screen
qtRectangle = self.frameGeometry()
centerPoint = QtWidgets.QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter(centerPoint)
self.move(qtRectangle.topLeft())
# self.move(QtGui.QApplication.desktop().screen().rect().center() - self.rect().center())
class holdButton(QtWidgets.QPushButton):
"""Subclass push button to count press time and update progress bar. handleClick() is overridden.
Popup with progress bar appears on click, fills as button is held."""
def __init__(self, NativeUI, *args, **kwargs):
super(holdButton, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
self.timeOut = 3000
self.timeStep = 60
self.setAutoRepeat(True)
self.setAutoRepeatDelay(self.timeStep)
self.setAutoRepeatInterval(self.timeStep)
self.pressTime = 0
self.state = 0
self.complete = False
self.clicked.connect(self.handleClick)
self.popUp = timerConfirmPopup(NativeUI)
self.popUp.okButton.pressed.connect(self.okButtonPressed)
self.popUp.cancelButton.pressed.connect(self.closePopup)
def handleClick(self):
"""Over ride handleClick to count button hold time.
Shows popup, sets progress bar, and updates it"""
self.now = time.time()
if self.state == 0:
# print('pressed')
self.popUp.show()
self.initial = time.time()
if self.isDown():
if self.pressTime == self.timeOut:
self.complete = True
logging.debug(self.now - self.initial)
self.popUp.stack.setCurrentWidget(self.popUp.completeWidg)
self.pressTime = self.pressTime + self.timeStep
self.state = 1
self.popUp.progressBar.setValue(self.pressTime)
self.popUp.progressBar.update()
else:
self.pressTime = 0
logging.debug(
"holdButton.handleClick():\nself.complete: %s\nself.state: %s"
% (self.complete, self.state)
)
if self.state == 1:
logging.debug("holdButton released")
if not self.complete:
self.popUp.close()
self.state = 0
def okButtonPressed(self):
"""Respond to ok button press by sending command corresponding to button type"""
logging.debug(self.text())
self.NativeUI.q_send_cmd(
"GENERAL", self.text()
) # text is stand stop or standby
self.closePopup()
return 0
def closePopup(self):
"""Reset progress bar and widget stack, and close popup"""
self.popUp.progressBar.setValue(0)
self.popUp.stack.setCurrentWidget(self.popUp.progressWidg)
self.popUp.close()
return 0
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
widg = holdButton("Standby")
widg.show()
sys.exit(app.exec_())
#!/usr/bin/env python3
"""
tab_modeswitch_button.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"
from PySide2 import QtCore, QtGui, QtWidgets
from widget_library.ok_cancel_buttons_widget import OkButtonWidget, CancelButtonWidget
import json
# from global_widgets.global_ok_cancel_buttons import okButton, cancelButton
class TabModeswitchButton(QtWidgets.QWidget):
modeSwitched = QtCore.Signal(str)
def __init__(self, NativeUI, *args, **kwargs):
super().__init__(*args, **kwargs)
"""Button opens popup for user to switch modes.
The label is updated to show the current operating mode"""
self.NativeUI = NativeUI
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)
self.setLayout(layout)
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):
"""Update button text to show operating mode"""
self.switchButton.setText(mode)
return 0
def switch_button_pressed(self):
"""Button pressed, open popup, ensure correct mode is selected in popup."""
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()
return 0
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):
def __init__(self, NativeUI, *args, **kwargs):
"""A popup used to switch modes. Allows the user to compare the values they are setting with current setting
and to navigate to mode setting page to edit those values."""
super(modeswitchPopup, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
with open("NativeUI/configs/mode_config.json") as json_file:
modeDict = json.load(json_file)
self.settingsList = modeDict['settings']
modeList = self.NativeUI.modeList
vradioLayout = QtWidgets.QVBoxLayout()
groupBox = QtWidgets.QGroupBox()
self.radioButtons = {}
for mode in modeList:
button = QtWidgets.QRadioButton(mode)
goto_button = QtWidgets.QPushButton(mode)
goto_button.pressed.connect(lambda j=mode: self.goto_pressed(j))
hlayout = QtWidgets.QHBoxLayout()
hlayout.addWidget(button)
hlayout.addWidget(goto_button)
self.radioButtons[mode] = button
vradioLayout.addLayout(hlayout)
groupBox.setLayout(vradioLayout)
## Values display
valuesLayout = QtWidgets.QHBoxLayout()
# Title labels:
initLabel = QtWidgets.QLabel(" ")
initVal = QtWidgets.QLabel("Current")
initVal.setAlignment(QtCore.Qt.AlignCenter)
newVal = QtWidgets.QLabel("New")
newVal.setAlignment(QtCore.Qt.AlignCenter)
newVal.setStyleSheet("color: red")
# Populate actual values in loop
self.labelList, self.currentLabelList, self.newLabelList = [], [], []
vlayout1, vlayout2, vlayout3 = (
QtWidgets.QVBoxLayout(),
QtWidgets.QVBoxLayout(),
QtWidgets.QVBoxLayout(),
)
vlayout1.addWidget(initLabel)
vlayout2.addWidget(initVal)
vlayout3.addWidget(newVal)
for settings in self.settingsList:
namelabel = QtWidgets.QLabel(settings[0])
namelabel.setAlignment(QtCore.Qt.AlignRight)
vlayout1.addWidget(namelabel)
currentLabel = QtWidgets.QLabel("0")
currentLabel.setAlignment(QtCore.Qt.AlignCenter)
self.currentLabelList.append(currentLabel)
vlayout2.addWidget(currentLabel)
newLabel = QtWidgets.QLabel("0")
newLabel.setAlignment(QtCore.Qt.AlignCenter)
newLabel.setStyleSheet("color: red")
self.newLabelList.append(newLabel)
vlayout3.addWidget(newLabel)
valuesLayout.addLayout(vlayout1)
valuesLayout.addLayout(vlayout2)
valuesLayout.addLayout(vlayout3)
hlayout = QtWidgets.QHBoxLayout()
hlayout.addWidget(groupBox)
hlayout.addLayout(valuesLayout)
## Ok Cancel Buttons
hbuttonlayout = QtWidgets.QHBoxLayout()
self.okbutton = OkButtonWidget(NativeUI)
self.okbutton.setEnabled(True)
self.okbutton.pressed.connect(self.ok_button_pressed)
self.cancelbutton = CancelButtonWidget(NativeUI)
self.cancelbutton.setEnabled(True)
self.cancelbutton.pressed.connect(self.cancel_button_pressed)
hbuttonlayout.addWidget(self.okbutton)
hbuttonlayout.addWidget(self.cancelbutton)
vlayout = QtWidgets.QVBoxLayout()
vlayout.addLayout(hlayout)
vlayout.addLayout(hbuttonlayout)
for mode in modeList:
button = self.radioButtons[mode]
button.pressed.connect(lambda i=button: self.update_settings_data(i))
if mode == self.NativeUI.currentMode:
button.click()
## Final, general, initiation steps
self.setLayout(vlayout)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
# self.radioButtons[self.NativeUI.currentMode].click() # 1st button clicked by default
# self.radioButtons[0].click() # 1st button clicked by default
# self.update_settings_data(radioButtons[0])
self.setStyleSheet(
"background-color:" + NativeUI.colors["page_background"].name() + ";"
"color:" + NativeUI.colors["page_foreground"].name() + ";"
"font: 16pt bold;"
)
# self.radioButtons[self.NativeUI.currentMode].click()
def goto_pressed(self, mode):
"""On button press, show mode page in UI"""
self.NativeUI.widgets.page_stack.setCurrentWidget(
self.NativeUI.widgets.modes_page
)
self.NativeUI.widgets.page_buttons.set_pressed(["modes_button"])
# Switch to the specific mode tab
for button in self.NativeUI.widgets.modes_page.widget_list[
0
].button_list: # mode_settings_tab.buttonWidgets:
if mode in button.text():
self.NativeUI.widgets.modes_page.widget_list[0].setTab(button)
# Close the popup
self.close()
def update_settings_data(self, button):
"""Respond to button press and update labels in modeswitch popup"""
self.spinDict = self.NativeUI.mode_handler.spinDict
self.mode = button.text()
for settings, currentLabel, newLabel in zip(
self.settingsList, self.currentLabelList, self.newLabelList
):
currentVal = self.spinDict[
"spin_" + self.NativeUI.currentMode + "_" + settings[2]
].get_value()
currentLabel.setText(str(round(currentVal, 4)))
setVal = self.spinDict["spin_" + self.mode + "_" + settings[2]].get_value()
newLabel.setText(str(round(setVal, 4)))
def ok_button_pressed(self):
"""Switch to selected mode"""
if self.NativeUI.currentMode == self.mode:
a = 1 # do nothing
else:
self.NativeUI.q_send_cmd(
"SET_MODE", self.mode.replace("/", "_").replace("-", "_")
)
self.NativeUI.currentMode = self.mode
self.close()
return 0
def cancel_button_pressed(self):
"""Close popup without doing anything"""
self.close()
return 0
def update_mode(self, mode):
"""When mode is changed the popup radio buttons should show the new mode"""
self.mode_popup.radioButtons[mode].click()
#!/usr/bin/env python3
"""
tab_start_stop_buttons.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 logging
from PySide2 import QtGui, QtWidgets
from PySide2.QtCore import QSize
from global_widgets.tab_hold_buttons import holdButton
class TabStartStopStandbyButtons(QtWidgets.QWidget):
"""
Combine holdButtons into Start, Stop, and Standby buttons in the left bar.
"""
def __init__(self, NativeUI, *args, size: QSize = None, **kwargs):
super(TabStartStopStandbyButtons, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
if size is not None:
self.__button_size = size
else:
self.__button_size = QSize(100, 20)
layout = QtWidgets.QVBoxLayout()
self.button_start = holdButton(NativeUI) # QtWidgets.QPushButton()
self.button_stop = holdButton(NativeUI) # QtWidgets.QPushButton()
self.button_standby = holdButton(NativeUI) # QtWidgets.QPushButton()
self.__buttons = [self.button_start, self.button_stop, self.button_standby]
self.__buttontext = ["START", "STOP", "STANDBY"]
self.__buttoncommand = [""]
for button, text in zip(self.__buttons, self.__buttontext):
button.setText(text)
button.popUp.completeLabel.setText("Ventilation " + text)
layout.addWidget(button)
button.setStyleSheet(
"background-color:" + NativeUI.colors["background_enabled"].name() + ";"
"border-color:" + NativeUI.colors["page_foreground"].name() + ";"
"color:" + NativeUI.colors["page_foreground"].name() + ";"
"border:none"
)
button.setFont(NativeUI.text_font)
button.setFixedSize(self.__button_size)
self.setLayout(layout)
#!/usr/bin/env python3
"""
template_main_pages.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"
from PySide2 import QtWidgets
# from global_widgets.global_send_popup import SetConfirmPopup
class TemplateMainPages(QtWidgets.QWidget):
def buildPage(self, buttonList, tabsList, *args, **kwargs):
vlayout = QtWidgets.QVBoxLayout()
hButtonLayout = QtWidgets.QHBoxLayout()
self.stack = QtWidgets.QStackedWidget()
for button, tab in zip(buttonList, tabsList):
hButtonLayout.addWidget(button)
self.stack.addWidget(tab)
button.pressed.connect(lambda i=button: self.setTab(i))
self.setTab(buttonList[0])
vlayout.addLayout(hButtonLayout)
vlayout.addWidget(self.stack)
self.setLayout(vlayout)
def setTab(self, buttonWidg):
for button, tab in zip(self.buttonWidgets, self.tabsList):
if button == buttonWidg:
button.setProperty("selected", "1")
self.stack.setCurrentWidget(tab)
else:
button.setProperty("selected", "0")
button.style().unpolish(button)
button.style().polish(button)
This diff is collapsed.
"""
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
#!/usr/bin/env python3
"""
hev_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"
from alarm_widgets.tab_alarms import TabAlarm
from alarm_widgets.tab_alarm_table import TabAlarmTable
from alarm_widgets.tab_clinical import TabClinical
from global_widgets.global_select_button import selectorButton
from global_widgets.template_main_pages import TemplateMainPages
from alarm_widgets.alarm_popup import abstractAlarm
from PySide2 import QtCore
class AlarmView(TemplateMainPages):
"""Subclasses TemplateMainPages to display alarms."""
def __init__(self, NativeUI, *args, **kwargs):
super(AlarmView, self).__init__(*args, **kwargs)
self.NativeUI = NativeUI
self.alarmButton = selectorButton(NativeUI, "List of Alarms")
self.alarmTableButton = selectorButton(NativeUI, "Alarm Table")
self.clinicalButton = selectorButton(NativeUI, "Clinical Limits")
# self.techButton = selectorButton(NativeUI, "Technical Limits")
self.buttonWidgets = [
self.alarmButton,
self.alarmTableButton,
self.clinicalButton,
] # , self.techButton]
self.alarmTab = TabAlarm(NativeUI)
self.alarmTableTab = TabAlarmTable(NativeUI)
self.clinicalTab = TabClinical(NativeUI)
# self.technicalTab = TabClinical(NativeUI)
self.tabsList = [
self.alarmTab,
self.alarmTableTab,
self.clinicalTab,
] # , self.technicalTab]
self.buildPage(self.buttonWidgets, self.tabsList)
self.alarmDict = {}
self.timer = QtCore.QTimer()
self.timer.setInterval(300)
self.timer.timeout.connect(self.updateAlarms)
self.timer.start()
def updateAlarms(self):
print("attempting new alarms")
newAlarmPayload = self.NativeUI.get_db("alarms")
if newAlarmPayload == []:
return
if newAlarmPayload["alarm_code"] in self.alarmDict:
a = 1
self.alarmDict[newAlarmPayload["alarm_code"]].resetTimer()
self.alarmDict[newAlarmPayload["alarm_code"]].calculateDuration()
else:
newAbstractAlarm = abstractAlarm(self.NativeUI, newAlarmPayload)
self.alarmDict[newAlarmPayload["alarm_code"]] = newAbstractAlarm
newAbstractAlarm.alarmExpired.connect(
lambda i=newAbstractAlarm: self.handleAlarmExpiry(i)
)
self.alarmTab.popup.addAlarm(newAbstractAlarm)
self.alarmTab.list.addAlarm(newAbstractAlarm)
self.alarmTableTab.table.addAlarmRow(newAbstractAlarm)
def handleAlarmExpiry(self, abstractAlarm):
abstractAlarm.freezeTimer()
self.alarmTab.popup.removeAlarm(abstractAlarm)
self.alarmTab.list.removeAlarm(abstractAlarm)
self.alarmDict.pop(abstractAlarm.alarmPayload["alarm_code"])
abstractAlarm.recordFinishTime()
import logging
import argparse
import sys
from PySide2.QtCore import Slot
from PySide2.QtWidgets import QWidget, QApplication, QHBoxLayout, QVBoxLayout
from hevclient import HEVClient
from tab_plots import TabPlots
class MainView(QWidget):
def __init__(self, *args, **kwargs):
super(MainView, self).__init__(*args, **kwargs)
hlayout = QHBoxLayout()
vlayout = QVBoxLayout()
self.tab_plots = TabPlots()
vlayout.addWidget(self.tab_plots)
hlayout.addLayout(vlayout)
self.setLayout(hlayout)
\ No newline at end of file
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 handle_cancel_pressed(self,buttonMode):
if buttonMode == self.NativeUI.currentMode:
print('modes match ')
self.commandSent()
else:
print('do nothing in clinical')
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, self.spinDict[widget].decPlaces)
message.append("set" + widget + " to " + str(setVal))
command.append(
[
self.spinDict[widget].cmd_type,
self.spinDict[widget].cmd_code,
setVal,
]
)
if isinstance(self.buttonDict[key], OkSendButtonWidget):
message.append("change mode to " + str(mode))
command.append(["SET_MODE", mode.replace("/", "_").replace("-", "_")])
# 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, self.mainSpinDict[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(self,message)
def sendCommands(self):
if self.commandList == []:
a=1
else:
for command in self.commandList:
print('sending commands')
print(command)
self.NativeUI.q_send_cmd(*command)
self.modeSwitched.emit(self.activeMode)
self.commandSent()
return 0
def handle_cancel_pressed(self, buttonMode):
for widget in self.spinDict:
if buttonMode in widget:
self.spinDict[widget].manuallyUpdated = False
if buttonMode == self.NativeUI.currentMode:
print('modes match ')
for widget in self.mainSpinDict:
self.mainSpinDict[widget].manuallyUpdated = False
else:
print('do nothing in clinical')
self.active_payload()
self.refresh_button_colour()
self.refresh_main_button_colour()
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):
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):
self.manuallyUpdatedBoolDict['CURRENT'] = False
for spin in self.mainSpinDict:
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
from PySide2 import QtWidgets, QtGui, QtCore
from global_widgets.global_select_button import selectorButton
# from global_widgets.global_spinbox import signallingSpinBox
from global_widgets.template_main_pages import TemplateMainPages
from global_widgets.template_set_values import TemplateSetValues
class TabModes(
TemplateMainPages
): # chose QWidget over QDialog family because easier to modify
def __init__(self, NativeUI, *args, **kwargs):
super(TabModes, self).__init__(NativeUI, *args, **kwargs)
self.NativeUI = NativeUI
self.settingsList = [
[
"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"],
]
hlayout = QtWidgets.QHBoxLayout()
hlayout.setSpacing(0)
self.pcacButton = selectorButton(NativeUI, "PC/AC")
self.pcacButton.setProperty("selected", "1")
self.pcacButton.style().polish(self.pcacButton)
self.pcacEnable = [1, 0, 1, 1, 0, 1, 0, 1]
self.pcacVals = [1, 2, 3, 4, 5, 6, 7, 8]
self.pcacPage = TemplateSetValues(NativeUI)
self.prvcButton = selectorButton(NativeUI, "PC/AC-PRVC")
self.prvcEnable = [1, 1, 0, 1, 0, 1, 1, 1]
self.prvcVals = [2, 3, 4, 5, 6, 7, 8, 9]
self.prvcPage = TemplateSetValues(NativeUI)
self.psvButton = selectorButton(NativeUI, "PC-PSV")
self.psvEnable = [1, 1, 0, 1, 0, 1, 0, 1]
self.psvVals = [3, 4, 5, 6, 7, 8, 9, 1]
self.psvPage = TemplateSetValues(NativeUI)
self.cpapButton = selectorButton(NativeUI, "CPAP")
self.cpapEnable = [1, 0, 1, 1, 0, 1, 0, 1]
self.cpapVals = [4, 5, 6, 7, 8, 9, 1, 2]
self.cpapPage = TemplateSetValues(NativeUI)
self.buttonWidgets = [
self.pcacButton,
self.prvcButton,
self.psvButton,
self.cpapButton,
]
enableList = [self.pcacEnable, self.prvcEnable, self.psvEnable, self.cpapEnable]
self.tabsDict = { 'PC/AC':self.pcacPage, 'PC/AC-PRVC':self.prvcPage, 'PC-PSV':self.psvPage, 'CPAP':self.cpapPage}
self.valsList = [self.pcacVals, self.prvcVals, self.psvVals, self.cpapVals]
self.modeList = self.NativeUI.modeList
self.spinDict = {}
for tab, mode, enable, vals in zip(
self.tabsDict.values(), self.modeList, enableList, self.valsList
):
#mode = mode.replace("/", "_")
#mode = mode.replace("-", "_")
tempSettingsList = [
[
target.replace("SET_TARGET_", "SET_TARGET_" + mode.replace("/", "_").replace("-", "_"))
for target in spinInfo
]
for spinInfo in self.settingsList
]
tab.addSpinSingleCol(tempSettingsList)
tab.mode = mode
tab.buttonGroup = QtWidgets.QButtonGroup()
for labelledSpin in tab.spinDict:
if tab.spinDict[labelledSpin].label == "Inhale Time":
tab.radioButtonTime = QtWidgets.QRadioButton()
tab.radioButtonTime.setChecked(bool(enable[1]))
tab.radioButtonTime.toggled.connect(
lambda i=tab.radioButtonTime, j=tab.spinDict[
labelledSpin
], k=tab.mode: self.radioPressed(i, j, k)
)
tab.spinDict[labelledSpin].insertWidget(tab.radioButtonTime, 1)
tab.buttonGroup.addButton(tab.radioButtonTime)
if tab.spinDict[labelledSpin].label == "IE Ratio":
tab.radioButtonRat = QtWidgets.QRadioButton()
tab.radioButtonRat.setChecked(bool(enable[2]))
tab.radioButtonRat.toggled.connect(
lambda i=tab.radioButtonRat, j=tab.spinDict[
labelledSpin
], k=tab.mode: self.radioPressed(i, j, k)
)
tab.spinDict[labelledSpin].insertWidget(tab.radioButtonRat, 1)
tab.buttonGroup.addButton(tab.radioButtonRat)
tab.addModeButtons()
tab.finaliseLayout()
self._setEnabled(tab, enable, vals)
self.spinDict[mode] = tab.spinDict
# self.addRadioButtons()
self.tabsList = self.tabsDict.values()
self.buildPage(self.buttonWidgets, self.tabsList)
# def addRadioButtons(self):
# for tab in self.tabsList:
# tab.buttonGroup = QtWidgets.QButtonGroup()
# for labelledSpin in tab.spinDict:
# if tab.spinDict[labelledSpin].label == "Inhale Time":
# tab.radioButtonTime = QtWidgets.QRadioButton()
# tab.radioButtonTime.toggled.connect(
# lambda i=tab.radioButtonTime, j=tab.spinDict[
# labelledSpin
# ], k=tab.mode: self.radioPressed(i, j, k)
# )
# tab.spinDict[labelledSpin].insertWidget(tab.radioButtonTime, 1)
# tab.buttonGroup.addButton(tab.radioButtonTime)
# if tab.spinDict[labelledSpin].label == "IE Ratio":
# tab.radioButtonRat = QtWidgets.QRadioButton()
# tab.radioButtonRat.toggled.connect(
# lambda i=tab.radioButtonRat, j=tab.spinDict[
# labelledSpin
# ]: self.radioPressed(i, j, k)
# )
# tab.spinDict[labelledSpin].insertWidget(tab.radioButtonRat, 1)
# tab.buttonGroup.addButton(tab.radioButtonRat)
def radioPressed(self, radioButtonState, labelledSpin, tabMode):
labelledSpin.simpleSpin.setEnabled(radioButtonState)
labelledSpin.simpleSpin.setProperty("bgColour", str(int(not radioButtonState)))
labelledSpin.simpleSpin.setProperty(
"textColour", str(int(not radioButtonState))
)
labelledSpin.simpleSpin.style().unpolish(labelledSpin.simpleSpin)
labelledSpin.simpleSpin.style().polish(labelledSpin.simpleSpin)
if tabMode == self.NativeUI.currentMode:
a=1
self.NativeUI.widgets.spin_buttons.setStackWidget(labelledSpin.label)
def _setColour(self, buttonWidg):
for button, box in zip(self.buttonWidgets, self.pageList):
if button == buttonWidg:
button.setProperty("selected", "1")
self.stack.setCurrentWidget(box)
else:
button.setProperty("selected", "0")
button.style().unpolish(button)
button.style().polish(button)
def _modeSwitch(self, box):
self.stack.setCurrentWidget(box)
def _setEnabled(self, box, enableList, values):
for widget, enableBool, value in zip(box.spinDict, enableList, values):
box.spinDict[widget].simpleSpin.setEnabled(enableBool)
if enableBool == 1:
box.spinDict[widget].simpleSpin.setProperty("bgColour", "0")
box.spinDict[widget].simpleSpin.setProperty("textColour", "0")
if enableBool == 0:
box.spinDict[widget].simpleSpin.setProperty("bgColour", "1")
box.spinDict[widget].simpleSpin.setProperty("textColour", "1")
box.spinDict[widget].simpleSpin.style().unpolish(
box.spinDict[widget].simpleSpin
)
box.spinDict[widget].simpleSpin.style().polish(
box.spinDict[widget].simpleSpin
)
box.spinDict[widget].simpleSpin.setValue(value)
#!/usr/bin/env python3
"""
tab_personal.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__ = "Development"
from PySide2 import QtWidgets, QtGui, QtCore
import sys
sys.path.append("~/Documents/hev/NativeUI/")
from global_widgets.template_set_values import TemplateSetValues
class TabPersonal(TemplateSetValues):
def __init__(self, *args, **kwargs):
super(TabPersonal, self).__init__(*args, **kwargs)
settingsList = [
["Name", "/min", "name", "SET_PERSONAL", "NAME"],
["Patient ID", "s", "patient_id", "SET_PERSONAL", "PATIENT_ID"],
["Age", "", "age", "SET_PERSONAL", "AGE"],
["Sex", "", "sex", "SET_PERSONAL", "SEX"],
["Weight", "", "weight", "SET_PERSONAL", "WEIGHT"],
["Height", "", "height", "SET_PERSONAL", "HEIGHT"],
]
textBoxes = ["Name", "Patient ID", "Sex"]
self.setPacketType("personal")
self.addPersonalCol(settingsList, textBoxes)
self.addButtons()
self.finaliseLayout()
@QtCore.Slot(dict)
def localise_text(self, text: dict) -> int:
self.spinDict["Name"].nameLabel.setText(text["personal_tab_name"])
self.spinDict["Patient ID"].nameLabel.setText(text["personal_tab_patientid"])
self.spinDict["Age"].nameLabel.setText(text["personal_tab_age"])
self.spinDict["Sex"].nameLabel.setText(text["personal_tab_sex"])
self.spinDict["Weight"].nameLabel.setText(text["personal_tab_weight"])
self.spinDict["Height"].nameLabel.setText(text["personal_tab_height"])
return 0
if __name__ == "__main__":
# sys.path.append("../")
app = QtWidgets.QApplication(sys.argv)
widg = TabPersonal()
widg.show()
sys.exit(app.exec_())
#!/usr/bin/env python3
import logging
import os
import pyqtgraph as pg
import numpy as np
from PySide2 import QtWidgets, QtCore
from pyqtgraph import PlotWidget, plot, mkColor
from hevclient import HEVClient
logging.basicConfig(level=logging.WARNING, format='%(asctime)s - %(levelname)s - %(message)s')
class TabPlots(QtWidgets.QWidget):
def __init__(self, port=54322, *args, **kwargs):
super(TabPlots, self).__init__(*args, **kwargs)
self.history_length = 500
self.time_range = 30
self.port = port
layout = QtWidgets.QVBoxLayout()
self.graphWidget = pg.GraphicsLayoutWidget()
layout.addWidget(self.graphWidget)
self.pressurePlot = self.graphWidget.addPlot(title="Pressure")
self.graphWidget.nextRow()
self.flowPlot = self.graphWidget.addPlot(title="Flow")
self.graphWidget.nextRow()
self.volumePlot = self.graphWidget.addPlot(title="Volume")
self.graphWidget.nextRow()
self.timestamp = list(el*(-1) for el in range(self.history_length))[::-1]
self.PID_P = list(0 for _ in range(self.history_length))
self.PID_I = list(0 for _ in range(self.history_length))
self.PID_D = list(0 for _ in range(self.history_length))
self.graphWidget.setBackground(mkColor(30, 30, 30))
# Add grid
self.flowPlot.showGrid(x=True, y=True)
self.volumePlot.showGrid(x=True, y=True)
self.pressurePlot.showGrid(x=True, y=True)
# Set Range
self.flowPlot.setXRange(self.time_range * (-1), 0, padding=0)
self.volumePlot.setXRange(self.time_range * (-1), 0, padding=0)
self.pressurePlot.setXRange(self.time_range * (-1), 0, padding=0)
self.flowPlot.enableAutoRange('y', True)
self.volumePlot.enableAutoRange('y', True)
self.pressurePlot.enableAutoRange('y', True)
# Plot styles
self.line1 = self.plot(self.pressurePlot, self.timestamp, self.PID_P, "Airway Pressure", "077")
self.line2 = self.plot(self.flowPlot, self.timestamp, self.PID_D, "Flow", "00F")
self.line3 = self.plot(self.volumePlot, self.timestamp, self.PID_I, "Volume", "707")
self.setLayout(layout)
self.timer = QtCore.QTimer()
self.timer.setInterval(16) # just faster than 60Hz
self.timer.timeout.connect(self.update_plot_data)
self.timer.start()
def plot(self, canvas, x, y, plotname, color):
pen = pg.mkPen(color=color, width=3)
return canvas.plot(x, y, name=plotname, pen=pen)
def update_plot_data(self):
# subtract latest timestamp and scale to seconds
timestamp = np.true_divide(np.subtract(self.parent().parent().plots[:, 0], self.parent().parent().plots[-1, 0]), 1000)
self.line1.setData(timestamp, self.parent().parent().plots[:, 1])
self.line2.setData(timestamp, self.parent().parent().plots[:, 2])
self.line3.setData(timestamp, self.parent().parent().plots[:, 3])
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
{
"on_mains_power": true,
"on_battery_power": false,
"battery_percent": 0.0,
"electrical_problem": null
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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