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()