From 5e5ecf902a32a9efe61e79a74f0e218060efc97a Mon Sep 17 00:00:00 2001 From: Jacob Deery Date: Wed, 9 Sep 2020 01:19:44 -0400 Subject: [PATCH 1/8] app/proc: Load real procedures and upgrade viz - Add a menu for opening a ProcLang file in the application (available with Ctrl-O) - Add utility functions to the ProceduresEngine for loading a new suite and resetting - Add visualization of ProcedureStep conditions/transitions - Add a bunch of TODOs --- application/application.py | 5 +- application/procedures_bridge.py | 100 +++++++----- application/resources/ProceduresPane.qml | 144 +++++++++++------- application/resources/application.qml | 6 + application/resources/example.proc | 17 +++ topside/procedures/procedures_engine.py | 65 ++++++-- .../tests/test_procedures_engine.py | 37 +++++ 7 files changed, 267 insertions(+), 107 deletions(-) create mode 100644 application/resources/example.proc diff --git a/application/application.py b/application/application.py index eacee6b..e9ffc3a 100644 --- a/application/application.py +++ b/application/application.py @@ -1,8 +1,9 @@ import os import sys -from PySide2.QtGui import QGuiApplication, QIcon +from PySide2.QtGui import QIcon from PySide2.QtQml import QQmlApplicationEngine, qmlRegisterType +from PySide2.QtWidgets import QApplication import topside as top from .visualization_area import VisualizationArea @@ -26,7 +27,7 @@ class Application: def __init__(self, argv): self.procedures_bridge = ProceduresBridge() - self.app = QGuiApplication(argv) + self.app = QApplication(argv) # ALL custom built QQuickItems have to be registed as QML objects in this way: qmlRegisterType(VisualizationArea, 'VisualizationArea', 1, 0, 'VisualizationArea') diff --git a/application/procedures_bridge.py b/application/procedures_bridge.py index 376775e..c7882aa 100644 --- a/application/procedures_bridge.py +++ b/application/procedures_bridge.py @@ -1,10 +1,11 @@ from PySide2.QtCore import Qt, Slot, Signal, Property, \ QObject, QAbstractListModel, QModelIndex +from PySide2.QtWidgets import QFileDialog import topside as top -# TODO(jacob): Delete this test code and load a real engine and procedure +# TODO(jacob): Delete this test code and load a real engine class MockPlumbingEngine: def __init__(self): @@ -20,41 +21,35 @@ def current_pressures(self): return {} -def build_test_procedure_suite(): - open_action_1 = top.StateChangeAction('c1', 'open') - open_action_2 = top.StateChangeAction('c2', 'open') - close_action_1 = top.StateChangeAction('c1', 'closed') - close_action_2 = top.StateChangeAction('c2', 'closed') +class ProcedureConditionWrapper(QObject): + satisfied_changed_signal = Signal(name='satisfiedChanged') - # Add miscellaneous action - misc_action = top.MiscAction('Approach the tower') + def __init__(self, condition, transition, parent=None): + QObject.__init__(self, parent=parent) + self._condition = condition + self._transition = transition - s1 = top.ProcedureStep('s1', open_action_1, [( - top.Immediate(), top.Transition('p1', 's2'))], 'PRIMARY') - s2 = top.ProcedureStep('s2', open_action_2, [( - top.Immediate(), top.Transition('p2', 's3'))], 'CONTROL') + # TODO(jacob): Make sure this actually updates properly once we're + # managing a real plumbing engine and stepping in time. + @Property(bool, notify=satisfied_changed_signal) + def satisfied(self): + return self._condition.satisfied() - # The step contain misc action - s5 = top.ProcedureStep('s5', misc_action, [( - top.Immediate(), top.Transition('p2', 's3'))], 'MISCELLANEOUS ACTION') + @Property(str, constant=True) + def condition(self): + return str(self._condition) - p1 = top.Procedure('p1', [s1, s2, s5]) - - s3 = top.ProcedureStep('s3', close_action_1, [( - top.Immediate(), top.Transition('p2', 's4'))], 'OPS') - s4 = top.ProcedureStep('s4', close_action_2, [( - top.Immediate(), top.Transition('p1', 's1'))], 'SECONDARY') - - p2 = top.Procedure('p2', [s3, s4]) - - return top.ProcedureSuite([p1, p2], 'p1') + @Property(str, constant=True) + def transition(self): + return str(self._transition) class ProcedureStepsModel(QAbstractListModel): - PersonRoleIdx = Qt.UserRole + 1 - StepRoleIdx = Qt.UserRole + 2 + ActionRoleIdx = Qt.UserRole + 1 + OperatorRoleIdx = Qt.UserRole + 2 + ConditionsRoleIdx = Qt.UserRole + 3 - def __init__(self, procedure, parent=None): + def __init__(self, procedure=None, parent=None): QAbstractListModel.__init__(self, parent) self.procedure = procedure @@ -66,6 +61,8 @@ def change_procedure(self, new_procedure): # Qt accessible methods def rowCount(self, parent=QModelIndex()): + if self.procedure is None: + return 0 return len(self.procedure.steps) def roleNames(self): @@ -75,24 +72,31 @@ def roleNames(self): # error ('expected hash, got dict'). See below: # https://bugreports.qt.io/browse/PYSIDE-703 return { - ProcedureStepsModel.PersonRoleIdx: b'person', - ProcedureStepsModel.StepRoleIdx: b'step' + ProcedureStepsModel.ActionRoleIdx: b'action', + ProcedureStepsModel.OperatorRoleIdx: b'operator', + ProcedureStepsModel.ConditionsRoleIdx: b'conditions' } def data(self, index, role): + if self.procedure is None: + return None + try: step = self.procedure.step_list[index.row()] except IndexError: return 'Invalid Index' - if role == ProcedureStepsModel.PersonRoleIdx: - return step.operator - elif role == ProcedureStepsModel.StepRoleIdx: + + if role == ProcedureStepsModel.ActionRoleIdx: action = step.action - # Check if misc action or not if type(action) == top.StateChangeAction: return f'Set {action.component} to {action.state}' elif type(action) == top.MiscAction: return f'{action.action_type}' + elif role == ProcedureStepsModel.OperatorRoleIdx: + return step.operator + elif role == ProcedureStepsModel.ConditionsRoleIdx: + return [ProcedureConditionWrapper(cond, trans, self) for cond, trans in step.conditions] + return None @@ -104,15 +108,20 @@ def __init__(self): QObject.__init__(self) plumb = MockPlumbingEngine() - suite = build_test_procedure_suite() - self._procedures_engine = top.ProceduresEngine(plumb, suite) - self._procedure_steps = ProcedureStepsModel(self._procedures_engine.current_procedure()) + self._procedures_engine = top.ProceduresEngine(plumb) + self._procedure_steps = ProcedureStepsModel() + self._refresh_procedure_view() def _refresh_procedure_view(self): proc = self._procedures_engine.current_procedure() + displayed_proc = self._procedure_steps.procedure - if self._procedure_steps.procedure.procedure_id != proc.procedure_id: + if proc is None: + self._procedure_steps.change_procedure(None) + return + + if displayed_proc is None or displayed_proc.procedure_id != proc.procedure_id: self._procedure_steps.change_procedure(proc) idx = proc.index_of(self._procedures_engine.current_step.step_id) @@ -122,6 +131,17 @@ def _refresh_procedure_view(self): def steps(self): return self._procedure_steps + @Slot() + def loadProcedureSuite(self): + # TODO(jacob): Make this menu remember the last file opened + filepath, _ = QFileDialog.getOpenFileName(self.parent(), 'Load ProcLang file') + if filepath != '': + with open(filepath) as f: + proclang = f.read() + suite = top.proclang.parse(proclang) + self._procedures_engine.load_suite(suite) + self._refresh_procedure_view() + @Slot() def playBackwards(self): pass @@ -140,7 +160,9 @@ def pause(self): @Slot() def stop(self): - pass + # TODO(jacob): Should this reset the plumbing engine as well? + self._procedures_engine.reset() + self._refresh_procedure_view() @Slot() def stepForward(self): diff --git a/application/resources/ProceduresPane.qml b/application/resources/ProceduresPane.qml index 237002f..2181c57 100644 --- a/application/resources/ProceduresPane.qml +++ b/application/resources/ProceduresPane.qml @@ -6,7 +6,6 @@ import QtQuick.Layouts 1.0 ColumnLayout { spacing: 0 - // TODO(jacob): Investigate if this connection logic can/should be moved into Python Component.onCompleted: { proceduresBridge.gotoStep.connect(proceduresList.gotoStep) } @@ -22,6 +21,7 @@ ColumnLayout { text: "PROCEDURES" color: "white" font.pointSize: 10 + font.weight: Font.Bold anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 10 @@ -32,11 +32,8 @@ ColumnLayout { id: proceduresList Layout.fillHeight: true Layout.fillWidth: true - Layout.leftMargin: 10 - Layout.rightMargin: 10 orientation: Qt.Vertical - spacing: 10 clip: true ScrollBar.vertical: ScrollBar { @@ -52,14 +49,6 @@ ColumnLayout { height: 10 Layout.fillWidth: true } - - highlight: Rectangle { - color: "lightgreen" - } - - highlightResizeDuration: 0 - highlightMoveDuration: 100 - highlightMoveVelocity: -1 boundsBehavior: Flickable.DragOverBounds @@ -74,6 +63,98 @@ ColumnLayout { } } + Component { + id: procedureStepDelegate + + Rectangle { + id: wrapper + width: proceduresList.width + height: procedureColumn.height + 10 + color: ListView.isCurrentItem ? "#d8fff7" : (index % 2 == 0 ? "#f2f2f2" : "#f9f9f9") + border.color: ListView.isCurrentItem ? "#800000" : "transparent" + + Column { + id: procedureColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 10 + anchors.rightMargin: 10 + + RowLayout { + id: stepRow + width: proceduresList.width + + Text { + text: index + 1 + "." + font.weight: Font.Bold + font.pointSize: 11 + Layout.alignment: Qt.AlignTop + } + + Text { + text: operator + ":" + color: "navy" + font.weight: Font.Bold + font.pointSize: 11 + Layout.alignment: Qt.AlignTop + } + + Text { + text: action + font.weight: wrapper.ListView.isCurrentItem ? Font.Bold : Font.Normal + font.pointSize: 11 + wrapMode: Text.Wrap + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + } + } + + // TODO(jacob): Do we really need a Loader here, or can we just insert the Repeater + // directly? + Loader { + visible: wrapper.ListView.isCurrentItem + sourceComponent: wrapper.ListView.isCurrentItem ? stepConditionsDelegate : null + onStatusChanged: if (status == Loader.Ready) item.model = conditions + } + } + } + } + + Component { + id: stepConditionsDelegate + + Column { + property alias model : conditionRepeater.model + + Rectangle { + height: 5 + width: proceduresList.width + color: "transparent" + } + + Repeater { + id: conditionRepeater + + delegate: Rectangle { + height: 30 + width: proceduresList.width + color: "transparent" + + Text { + // TODO(jacob): Figure out how to get this text to wrap + anchors.verticalCenter: parent.verticalCenter + x: 30 + font.pointSize: 10 + font.weight: modelData.satisfied ? Font.Bold : Font.Normal + color: modelData.satisfied ? "#005000" : "#4d4d4d" + text: "[" + modelData.condition + "] " + modelData.transition + } + } + } + } + } + Rectangle { Layout.alignment: Qt.AlignBottom Layout.minimumHeight: 100 @@ -136,14 +217,7 @@ ColumnLayout { icon.source: "themes/default/step_forward.png" icon.color: "transparent" - // TODO(jacob): We connect both clicked and doubleClicked to stepForward because - // there seems to be some sort of debouncing/delay built in that prevents clicking - // twice in quick succession from advancing steps twice. For now, we choose to - // explicitly recognize a double click as identical to a click. Investigate if this - // is really the best way to do things or if there's a better way to get rid of the - // delay. onClicked: proceduresBridge.stepForward() - onDoubleClicked: proceduresBridge.stepForward() } Button { Layout.alignment: Qt.AlignVCenter @@ -156,36 +230,4 @@ ColumnLayout { } } } - - Component { - id: procedureStepDelegate - - RowLayout { - width: proceduresList.width - spacing: 10 - - Text { - text: model.index + 1 + "." - font.bold: true - font.pointSize: 10 - Layout.alignment: Qt.AlignTop - } - - Text { - text: model.person + ":" - font.bold: true - font.pointSize: 10 - color: "blue" - Layout.alignment: Qt.AlignTop - } - - Text { - text: model.step - font.pointSize: 10 - wrapMode: Text.Wrap - Layout.fillWidth: true - Layout.alignment: Qt.AlignTop - } - } - } } diff --git a/application/resources/application.qml b/application/resources/application.qml index d7107be..42dae52 100644 --- a/application/resources/application.qml +++ b/application/resources/application.qml @@ -21,6 +21,12 @@ ApplicationWindow { Menu { title: "&File" + Action { + text: "&Open ProcLang" + shortcut: StandardKey.Open + onTriggered: proceduresBridge.loadProcedureSuite() + } + Action { text: "&Quit" shortcut: StandardKey.Quit diff --git a/application/resources/example.proc b/application/resources/example.proc new file mode 100644 index 0000000..dc8d52d --- /dev/null +++ b/application/resources/example.proc @@ -0,0 +1,17 @@ +main: + 1. PRIMARY: set series_fill_valve to closed + 2. PRIMARY: set supply_valve to open + - [p1 < 600] abort_1.1 + - [p1 > 1000] abort_2.1 + 3. PRIMARY: set series_fill_valve to open + 4. PRIMARY: set remote_fill_valve to open + 5. PRIMARY: [180s] set remote_fill_valve to closed + 6. PRIMARY: set remote_vent_valve to open + +abort_1: + 1. SECONDARY: set supply_valve to closed + 2. SECONDARY: set remote_vent_valve to open + +abort_2: + 1. CONTROL: set supply_valve to closed + 2. CONTROL: set remote_vent_valve to open diff --git a/topside/procedures/procedures_engine.py b/topside/procedures/procedures_engine.py index 58734cd..88b0bea 100644 --- a/topside/procedures/procedures_engine.py +++ b/topside/procedures/procedures_engine.py @@ -26,14 +26,40 @@ def __init__(self, plumbing_engine=None, suite=None): including information about the starting procedure. """ self._plumb = plumbing_engine - self._suite = suite + self._suite = None self.current_procedure_id = None self.current_step = None if suite is not None: + self.load_suite(suite) + + def reset(self): + """ + Return to the starting procedure and step of the managed suite. + + Does NOT affect the managed plumbing engine. + """ + # TODO(jacob): When we load a new procedure, we start in the + # first step, but that means that the action for that step never + # gets executed (we just start waiting on its conditions + # immediately). Figure out an elegant way to resolve this. + if self._suite is not None: self.current_procedure_id = self._suite.starting_procedure_id self.current_step = self._suite[self.current_procedure_id].step_list[0] + def load_suite(self, suite): + """ + Stop managing the current ProcedureSuite and manage a new one. + + Parameters + ---------- + + suite: topside.ProcedureSuite + The new ProcedureSuite that should be loaded. + """ + self._suite = suite + self.reset() + def execute(self, action): """Execute an action on the managed PlumbingEngine if it is not a Miscellaneous Action""" if type(action) == top.StateChangeAction: @@ -43,12 +69,13 @@ def update_conditions(self): """ Update all current conditions by querying the managed plumbing engine. """ - time = self._plumb.time - pressures = self._plumb.current_pressures() - state = {'time': time, 'pressures': pressures} + if self._plumb is not None: + time = self._plumb.time + pressures = self._plumb.current_pressures() + state = {'time': time, 'pressures': pressures} - for condition, _ in self.current_step.conditions: - condition.update(state) + for condition, _ in self.current_step.conditions: + condition.update(state) def ready_to_advance(self): """ @@ -56,6 +83,9 @@ def ready_to_advance(self): Returns True if any condition is satisfied, and False otherwise. """ + if self.current_step is None: + return False + for condition, _ in self.current_step.conditions: if condition.satisfied(): return True @@ -69,13 +99,14 @@ def next_step(self): first one (the highest priority one). If no conditions are satisfied, this function does nothing. """ - for condition, transition in self.current_step.conditions: - if condition.satisfied(): - new_proc = transition.procedure - self.current_procedure_id = new_proc - self.current_step = self._suite[new_proc].steps[transition.step] - self.execute(self.current_step.action) - break + if self.current_step is not None: + for condition, transition in self.current_step.conditions: + if condition.satisfied(): + new_proc = transition.procedure + self.current_procedure_id = new_proc + self.current_step = self._suite[new_proc].steps[transition.step] + self.execute(self.current_step.action) + break def step_time(self, timestep=None): """ @@ -87,8 +118,9 @@ def step_time(self, timestep=None): The number of microseconds that the managed plumbing engine should be stepped in time by. """ - self._plumb.step(timestep) - self.update_conditions() + if self._plumb is not None: + self._plumb.step(timestep) + self.update_conditions() # TODO(jacob): Consider if this function should return anything # about the state of the system: plumbing engine state, current @@ -96,4 +128,7 @@ def step_time(self, timestep=None): def current_procedure(self): """Return the procedure currently being executed.""" + if self._suite is None: + return None + return self._suite[self.current_procedure_id] diff --git a/topside/procedures/tests/test_procedures_engine.py b/topside/procedures/tests/test_procedures_engine.py index c3cb4fa..173e51c 100644 --- a/topside/procedures/tests/test_procedures_engine.py +++ b/topside/procedures/tests/test_procedures_engine.py @@ -93,6 +93,43 @@ def branching_procedure_suite_two_options(): return top.ProcedureSuite([proc_1, proc_2], 'p1') +def test_load_suite(): + p1s1 = top.ProcedureStep('p1s1', top.Action('injector_valve', 'open'), []) + suite_1 = top.ProcedureSuite([top.Procedure('p1', [p1s1])], 'p1') + + p2s1 = top.ProcedureStep('p2s1', top.Action('injector_valve', 'closed'), []) + suite_2 = top.ProcedureSuite([top.Procedure('p2', [p2s1])], 'p2') + + proc_eng = top.ProceduresEngine(None, suite_1) + + assert proc_eng._suite == suite_1 + assert proc_eng.current_procedure_id == 'p1' + assert proc_eng.current_step == p1s1 + + proc_eng.load_suite(suite_2) + + assert proc_eng._suite == suite_2 + assert proc_eng.current_procedure_id == 'p2' + assert proc_eng.current_step == p2s1 + + +def test_reset(): + plumb_eng = one_component_engine() + proc_eng = top.ProceduresEngine(plumb_eng, branching_procedure_suite_one_option()) + + assert proc_eng.current_procedure_id == 'p1' + assert proc_eng.current_step.step_id == 's1' + assert plumb_eng.current_state('c1') == 'closed' + proc_eng.next_step() + assert proc_eng.current_procedure_id == 'p2' + assert proc_eng.current_step.step_id == 's3' + assert plumb_eng.current_state('c1') == 'open' + proc_eng.reset() + assert proc_eng.current_procedure_id == 'p1' + assert proc_eng.current_step.step_id == 's1' + assert plumb_eng.current_state('c1') == 'open' # Plumbing engine is unaffected by reset() + + def test_execute_custom_action(): plumb_eng = one_component_engine() proc_eng = top.ProceduresEngine(plumb_eng) From b28b814a33ddddb4f067091ffe2c844716b27469 Mon Sep 17 00:00:00 2001 From: Jacob Deery Date: Mon, 14 Sep 2020 13:32:52 -0400 Subject: [PATCH 2/8] Fix a bug when changing procedures --- application/procedures_bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/procedures_bridge.py b/application/procedures_bridge.py index c7882aa..be991de 100644 --- a/application/procedures_bridge.py +++ b/application/procedures_bridge.py @@ -121,7 +121,7 @@ def _refresh_procedure_view(self): self._procedure_steps.change_procedure(None) return - if displayed_proc is None or displayed_proc.procedure_id != proc.procedure_id: + if displayed_proc != proc: self._procedure_steps.change_procedure(proc) idx = proc.index_of(self._procedures_engine.current_step.step_id) From b1926f52ba54f0f9356417387165cf00b3bacadd Mon Sep 17 00:00:00 2001 From: Jacob Deery Date: Fri, 18 Sep 2020 20:09:20 -0400 Subject: [PATCH 3/8] Use named properties for colours --- application/resources/ProceduresPane.qml | 25 ++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/application/resources/ProceduresPane.qml b/application/resources/ProceduresPane.qml index 2181c57..9701f84 100644 --- a/application/resources/ProceduresPane.qml +++ b/application/resources/ProceduresPane.qml @@ -6,6 +6,9 @@ import QtQuick.Layouts 1.0 ColumnLayout { spacing: 0 + property string procHeaderBg: "#888888" + property string procHeaderTxt: "#ffffff" + Component.onCompleted: { proceduresBridge.gotoStep.connect(proceduresList.gotoStep) } @@ -15,11 +18,11 @@ ColumnLayout { Layout.minimumHeight: 30 Layout.preferredHeight: 30 Layout.fillWidth: true - color: "#888888" + color: procHeaderBg Text { text: "PROCEDURES" - color: "white" + color: procHeaderTxt font.pointSize: 10 font.weight: Font.Bold anchors.verticalCenter: parent.verticalCenter @@ -67,11 +70,16 @@ ColumnLayout { id: procedureStepDelegate Rectangle { + property string stepHighlightBg: "#d8fff7" + property string stepHighlightBorder: "#800000" + property string stepEvenIdxBg: "#f2f2f2" + property string stepOddIdxBg: "#f9f9f9" + id: wrapper width: proceduresList.width height: procedureColumn.height + 10 - color: ListView.isCurrentItem ? "#d8fff7" : (index % 2 == 0 ? "#f2f2f2" : "#f9f9f9") - border.color: ListView.isCurrentItem ? "#800000" : "transparent" + color: ListView.isCurrentItem ? stepHighlightBg : (index % 2 == 0 ? stepEvenIdxBg : stepOddIdxBg) + border.color: ListView.isCurrentItem ? stepHighlightBorder : "transparent" Column { id: procedureColumn @@ -142,12 +150,15 @@ ColumnLayout { color: "transparent" Text { + property string condSatisfiedTxt: "#005000" + property string condNotSatisfiedTxt: "#4d4d4d" + // TODO(jacob): Figure out how to get this text to wrap anchors.verticalCenter: parent.verticalCenter x: 30 font.pointSize: 10 font.weight: modelData.satisfied ? Font.Bold : Font.Normal - color: modelData.satisfied ? "#005000" : "#4d4d4d" + color: modelData.satisfied ? condSatisfiedTxt : condNotSatisfiedTxt text: "[" + modelData.condition + "] " + modelData.transition } } @@ -156,11 +167,13 @@ ColumnLayout { } Rectangle { + property string procFooterBg: "#007700" + Layout.alignment: Qt.AlignBottom Layout.minimumHeight: 100 Layout.preferredHeight: 100 Layout.fillWidth: true - color: "green" + color: procFooterBg RowLayout { anchors.verticalCenter: parent.verticalCenter From 74acc5a02c2c9e7431e01a639e51d2dd6fa6f65c Mon Sep 17 00:00:00 2001 From: Jacob Deery Date: Fri, 2 Oct 2020 20:32:30 -0400 Subject: [PATCH 4/8] Fix test that rebase broke --- topside/procedures/tests/test_procedures_engine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topside/procedures/tests/test_procedures_engine.py b/topside/procedures/tests/test_procedures_engine.py index 173e51c..90b8e63 100644 --- a/topside/procedures/tests/test_procedures_engine.py +++ b/topside/procedures/tests/test_procedures_engine.py @@ -94,10 +94,10 @@ def branching_procedure_suite_two_options(): def test_load_suite(): - p1s1 = top.ProcedureStep('p1s1', top.Action('injector_valve', 'open'), []) + p1s1 = top.ProcedureStep('p1s1', top.Action('injector_valve', 'open'), [], 'PRIMARY') suite_1 = top.ProcedureSuite([top.Procedure('p1', [p1s1])], 'p1') - p2s1 = top.ProcedureStep('p2s1', top.Action('injector_valve', 'closed'), []) + p2s1 = top.ProcedureStep('p2s1', top.Action('injector_valve', 'closed'), [], 'SECONDARY') suite_2 = top.ProcedureSuite([top.Procedure('p2', [p2s1])], 'p2') proc_eng = top.ProceduresEngine(None, suite_1) From 58d9f785c26c21da3ca2f5d81a05da7a18168bf3 Mon Sep 17 00:00:00 2001 From: Jacob Deery Date: Sat, 3 Oct 2020 15:36:58 -0400 Subject: [PATCH 5/8] Make the example ProcLang file more interesting --- application/resources/example.proc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/application/resources/example.proc b/application/resources/example.proc index dc8d52d..febbcd0 100644 --- a/application/resources/example.proc +++ b/application/resources/example.proc @@ -4,14 +4,14 @@ main: - [p1 < 600] abort_1.1 - [p1 > 1000] abort_2.1 3. PRIMARY: set series_fill_valve to open - 4. PRIMARY: set remote_fill_valve to open - 5. PRIMARY: [180s] set remote_fill_valve to closed - 6. PRIMARY: set remote_vent_valve to open + 4. CONTROL: set remote_fill_valve to open + 5. CONTROL: [180s] set remote_fill_valve to closed + 6. CONTROL: set remote_vent_valve to open abort_1: - 1. SECONDARY: set supply_valve to closed - 2. SECONDARY: set remote_vent_valve to open + 1. PRIMARY: set supply_valve to closed + 2. CONTROL: set remote_vent_valve to open abort_2: - 1. CONTROL: set supply_valve to closed + 1. PRIMARY: set supply_valve to closed 2. CONTROL: set remote_vent_valve to open From 9182645402e2ae0c874a1803f0bd5c47ba57e567 Mon Sep 17 00:00:00 2001 From: Jacob Deery Date: Sat, 3 Oct 2020 15:45:23 -0400 Subject: [PATCH 6/8] Fix another test --- topside/procedures/tests/test_procedures_engine.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/topside/procedures/tests/test_procedures_engine.py b/topside/procedures/tests/test_procedures_engine.py index 90b8e63..afac04d 100644 --- a/topside/procedures/tests/test_procedures_engine.py +++ b/topside/procedures/tests/test_procedures_engine.py @@ -94,10 +94,11 @@ def branching_procedure_suite_two_options(): def test_load_suite(): - p1s1 = top.ProcedureStep('p1s1', top.Action('injector_valve', 'open'), [], 'PRIMARY') + p1s1 = top.ProcedureStep('p1s1', top.StateChangeAction('injector_valve', 'open'), [], 'PRIMARY') suite_1 = top.ProcedureSuite([top.Procedure('p1', [p1s1])], 'p1') - p2s1 = top.ProcedureStep('p2s1', top.Action('injector_valve', 'closed'), [], 'SECONDARY') + p2s1 = top.ProcedureStep('p2s1', top.StateChangeAction('injector_valve', 'closed'), [], + 'SECONDARY') suite_2 = top.ProcedureSuite([top.Procedure('p2', [p2s1])], 'p2') proc_eng = top.ProceduresEngine(None, suite_1) From e8c6323e9874afbf879f2bee932f556478e4f242 Mon Sep 17 00:00:00 2001 From: Jacob Deery Date: Sat, 3 Oct 2020 15:47:14 -0400 Subject: [PATCH 7/8] Use named property for colour --- application/resources/ProceduresPane.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/resources/ProceduresPane.qml b/application/resources/ProceduresPane.qml index 9701f84..7fd9a6e 100644 --- a/application/resources/ProceduresPane.qml +++ b/application/resources/ProceduresPane.qml @@ -74,6 +74,7 @@ ColumnLayout { property string stepHighlightBorder: "#800000" property string stepEvenIdxBg: "#f2f2f2" property string stepOddIdxBg: "#f9f9f9" + property string stepOperatorTxt: "#002060" id: wrapper width: proceduresList.width @@ -102,7 +103,7 @@ ColumnLayout { Text { text: operator + ":" - color: "navy" + color: stepOperatorTxt font.weight: Font.Bold font.pointSize: 11 Layout.alignment: Qt.AlignTop From da73d0bc21ee0b046ec03d034eb84a62b4d5d0af Mon Sep 17 00:00:00 2001 From: Jacob Deery Date: Sat, 3 Oct 2020 20:30:00 -0400 Subject: [PATCH 8/8] Guard against `None` --- topside/procedures/procedures_engine.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/topside/procedures/procedures_engine.py b/topside/procedures/procedures_engine.py index 88b0bea..fc97e40 100644 --- a/topside/procedures/procedures_engine.py +++ b/topside/procedures/procedures_engine.py @@ -63,7 +63,8 @@ def load_suite(self, suite): def execute(self, action): """Execute an action on the managed PlumbingEngine if it is not a Miscellaneous Action""" if type(action) == top.StateChangeAction: - self._plumb.set_component_state(action.component, action.state) + if self._plumb is not None: + self._plumb.set_component_state(action.component, action.state) def update_conditions(self): """