AutoPumping 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- auto_pumping/__init__.py +116 -0
- auto_pumping/__main__.py +35 -0
- auto_pumping/config.py +351 -0
- auto_pumping/dummy.py +49 -0
- auto_pumping/executor.py +178 -0
- auto_pumping/graph.py +358 -0
- auto_pumping/safety.py +83 -0
- auto_pumping/ui.py +691 -0
- autopumping-0.1.0.dist-info/METADATA +72 -0
- autopumping-0.1.0.dist-info/RECORD +13 -0
- autopumping-0.1.0.dist-info/WHEEL +5 -0
- autopumping-0.1.0.dist-info/licenses/LICENSE +28 -0
- autopumping-0.1.0.dist-info/top_level.txt +1 -0
auto_pumping/__init__.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from .safety import SafetyMachine
|
|
3
|
+
from .graph import PumpingGraph
|
|
4
|
+
from .config import Config
|
|
5
|
+
from .executor import PlanExecutor
|
|
6
|
+
from .ui import UI
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AutoPumping(ABC):
|
|
10
|
+
CONFIG_CLASS = Config
|
|
11
|
+
DIAGRAM_PATH = None
|
|
12
|
+
|
|
13
|
+
def __init__(self, config, ui=True):
|
|
14
|
+
self._config = config
|
|
15
|
+
self._safety_machine = SafetyMachine(config, self)
|
|
16
|
+
self._pumping_graph = PumpingGraph(config)
|
|
17
|
+
self._plan_executor = PlanExecutor(self)
|
|
18
|
+
self._ui = None
|
|
19
|
+
if ui:
|
|
20
|
+
self._ui = UI(config, self, diagram=self.DIAGRAM_PATH)
|
|
21
|
+
|
|
22
|
+
def run(self):
|
|
23
|
+
if self._ui is None:
|
|
24
|
+
raise RuntimeError("UI is not enabled for this instance.")
|
|
25
|
+
self._ui.run()
|
|
26
|
+
|
|
27
|
+
def auto(self, **goal_state_data):
|
|
28
|
+
self._plan_executor.execute_plan(
|
|
29
|
+
list(self._pumping_graph.make_plan(self._get_state(), **goal_state_data))
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def cancel(self):
|
|
33
|
+
self._plan_executor.cancel()
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def plan(self):
|
|
37
|
+
return self._plan_executor.plan
|
|
38
|
+
|
|
39
|
+
def _get_state(self):
|
|
40
|
+
return {
|
|
41
|
+
"valves": {
|
|
42
|
+
valve: self.get_valve_state(valve) for valve in self._config.valves
|
|
43
|
+
},
|
|
44
|
+
"pumps": {pump: self.get_pump_state(pump) for pump in self._config.pumps},
|
|
45
|
+
"stage": self.get_stage_position(),
|
|
46
|
+
"gauges": {gauge: self._pumping_graph._get_gauge_range(gauge, self.get_gauge_pressure(gauge)) for gauge in self._config.gauges},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_config_file(cls, config_file):
|
|
51
|
+
config = cls.CONFIG_CLASS.load_file(config_file)
|
|
52
|
+
return cls(config)
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def get_valve_state(self, valve):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def _actuate_valve(self, valve, state):
|
|
59
|
+
if self._safety_machine.can_actuate_valve(valve, state):
|
|
60
|
+
self.actuate_valve(valve, state)
|
|
61
|
+
self._safety_machine.record_valve_change(valve)
|
|
62
|
+
return True
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def actuate_valve(self, valve, state):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def get_pump_state(self, pump):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def _set_pump(self, pump, state):
|
|
74
|
+
if self._safety_machine.can_set_pump(pump, state):
|
|
75
|
+
self.set_pump(pump, state)
|
|
76
|
+
self._safety_machine.record_pump_change(pump)
|
|
77
|
+
return True
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def set_pump(self, pump, state):
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
def get_pump_current(self, pump):
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def get_pump_voltage(self, pump):
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def get_pump_flow(self, pump):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def get_pump_speed(self, pump):
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def get_gauge_pressure(self, gauge):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
@abstractmethod
|
|
105
|
+
def get_stage_position(self):
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
def _move_stage(self, position):
|
|
109
|
+
if self._safety_machine.can_move_stage(position):
|
|
110
|
+
self.move_stage(position)
|
|
111
|
+
return True
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
@abstractmethod
|
|
115
|
+
def move_stage(self, position):
|
|
116
|
+
pass
|
auto_pumping/__main__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from .dummy import DummyPumping
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def build_parser(system_class):
|
|
6
|
+
parser = argparse.ArgumentParser()
|
|
7
|
+
parser.add_argument(
|
|
8
|
+
"-c",
|
|
9
|
+
"--config",
|
|
10
|
+
type=str,
|
|
11
|
+
required=True,
|
|
12
|
+
help="Path to the configuration file.",
|
|
13
|
+
)
|
|
14
|
+
subparsers = parser.add_subparsers(dest="command", help="Sub-command help")
|
|
15
|
+
network_parser = subparsers.add_parser("network", help="Draw the network graph.")
|
|
16
|
+
network_parser.add_argument(
|
|
17
|
+
"--output", "-o", type=str, help="Output file for the network graph."
|
|
18
|
+
)
|
|
19
|
+
run_parser = subparsers.add_parser("run", help="Run the pumping system.")
|
|
20
|
+
|
|
21
|
+
return parser
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main(system_class):
|
|
25
|
+
parser = build_parser(system_class)
|
|
26
|
+
args = parser.parse_args()
|
|
27
|
+
system = system_class.from_config_file(args.config)
|
|
28
|
+
if args.command == "network":
|
|
29
|
+
system._pumping_graph.draw(args.output)
|
|
30
|
+
elif args.command == "run":
|
|
31
|
+
system.run()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
main(DummyPumping)
|
auto_pumping/config.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator, field_validator
|
|
2
|
+
from enum import Enum
|
|
3
|
+
import yaml
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ValveState(str, Enum):
|
|
7
|
+
OPEN = "open"
|
|
8
|
+
CLOSED = "closed"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PumpState(str, Enum):
|
|
12
|
+
ON = "on"
|
|
13
|
+
OFF = "off"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StrictModel(BaseModel):
|
|
17
|
+
model_config = ConfigDict(extra="forbid")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class NamedState(StrictModel):
|
|
21
|
+
valves: dict[str, ValveState] = Field({}, description="A mapping of valve names to their states in this named state (not all valves need to be specified)")
|
|
22
|
+
stage: str | None = Field(None, description="Stage position in this named state")
|
|
23
|
+
pumps: dict[str, PumpState] = Field({}, description="A mapping of pump names to their states in this named state (not all pumps need to be specified)")
|
|
24
|
+
|
|
25
|
+
def _check_valves(self, valves: list[str]):
|
|
26
|
+
for valve in self.valves.keys():
|
|
27
|
+
if valve not in valves:
|
|
28
|
+
raise ValueError(f"Valve {valve} not in valves list")
|
|
29
|
+
|
|
30
|
+
def _check_pumps(self, pumps: list[str]):
|
|
31
|
+
for pump in self.pumps.keys():
|
|
32
|
+
if pump not in pumps:
|
|
33
|
+
raise ValueError(f"Pump {pump} not in pumps list")
|
|
34
|
+
|
|
35
|
+
def _check_stage(self, stages: list[str]):
|
|
36
|
+
if self.stage is not None and self.stage not in stages:
|
|
37
|
+
raise ValueError(f"Stage position {self.stage} not in stage position list")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Inequality(StrictModel):
|
|
41
|
+
LE: float | None = Field(None, description="Upper bound of the inequality (less than or equal to)")
|
|
42
|
+
GE: float | None = Field(None, description="Lower bound of the inequality (greater than or equal to)")
|
|
43
|
+
|
|
44
|
+
@model_validator(mode="after")
|
|
45
|
+
def check_inequality(self):
|
|
46
|
+
if self.LE is not None and self.GE is not None and self.LE < self.GE:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"LE {self.LE} must be greater than or equal to GE {self.GE}"
|
|
49
|
+
)
|
|
50
|
+
if self.LE is None and self.GE is None:
|
|
51
|
+
raise ValueError("At least one of LE or GE must be defined")
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
def evaluate(self, value: float) -> bool:
|
|
55
|
+
if self.LE is not None and value > self.LE:
|
|
56
|
+
return False
|
|
57
|
+
if self.GE is not None and value < self.GE:
|
|
58
|
+
return False
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
def to_str(self, variable):
|
|
62
|
+
if self.LE is not None and self.GE is not None:
|
|
63
|
+
return f"{self.GE} <= {variable} <= {self.LE}"
|
|
64
|
+
elif self.LE is not None:
|
|
65
|
+
return f"{variable} <= {self.LE}"
|
|
66
|
+
elif self.GE is not None:
|
|
67
|
+
return f"{variable} >= {self.GE}"
|
|
68
|
+
raise ValueError("Inequality must have at least one of LE or GE defined")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class PumpCondition(StrictModel):
|
|
72
|
+
state: PumpState | None = Field(None, description="The required state of the pump for the transition to be valid")
|
|
73
|
+
time: float | None = Field(None, description="Time in seconds that the pump must be in the required state before the transition is valid")
|
|
74
|
+
current: Inequality | None = Field(None, description="Require the pump current draw to be within the range specified by the inequality for the transition to be valid")
|
|
75
|
+
voltage: Inequality | None = Field(None, description="Require the pump voltage to be within the range specified by the inequality for the transition to be valid")
|
|
76
|
+
flow: Inequality | None = Field(None, description="Require the pump flow rate to be within the range specified by the inequality for the transition to be valid")
|
|
77
|
+
speed: Inequality | None = Field(None, description="Require the pump speed to be within the range specified by the inequality for the transition to be valid")
|
|
78
|
+
|
|
79
|
+
@model_validator(mode="before")
|
|
80
|
+
def str2model(cls, v):
|
|
81
|
+
if isinstance(v, str):
|
|
82
|
+
return {"state": v}
|
|
83
|
+
return v
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class SafetyConditions(StrictModel):
|
|
87
|
+
pump: dict[str, PumpCondition] | None = Field(None, description="A mapping of pump names to conditions under which the transition should be automatically executed to prevent damage to the system")
|
|
88
|
+
|
|
89
|
+
def _check_pumps(self, pumps: list[str]):
|
|
90
|
+
if self.pump is not None:
|
|
91
|
+
for pump in self.pump.keys():
|
|
92
|
+
if pump not in pumps:
|
|
93
|
+
raise ValueError(f"Pump {pump} not in pumps list")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class PressureChange(StrictModel):
|
|
97
|
+
time: float = Field(..., description="Time in minutes that it is expected it will take to reach the specified pressure")
|
|
98
|
+
gauge: str = Field(..., description="Name of the gauge for the pressure change")
|
|
99
|
+
pressure: float = Field(..., description="Expected gauge pressure after the specified time has elapsed")
|
|
100
|
+
|
|
101
|
+
@model_validator(mode="before")
|
|
102
|
+
def dict2fields(cls, raw_data):
|
|
103
|
+
transformed_data = {}
|
|
104
|
+
for key, val in raw_data.items():
|
|
105
|
+
if key not in cls.model_fields:
|
|
106
|
+
if isinstance(key, str):
|
|
107
|
+
transformed_data["gauge"] = key
|
|
108
|
+
transformed_data["pressure"] = val
|
|
109
|
+
continue
|
|
110
|
+
transformed_data[key] = val
|
|
111
|
+
return transformed_data
|
|
112
|
+
|
|
113
|
+
def _check_gauges(self, gauges: list[str]):
|
|
114
|
+
if self.gauge not in gauges:
|
|
115
|
+
raise ValueError(f"Gauge {self.gauge} not in gauges list")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class ValveCondition(StrictModel):
|
|
119
|
+
state: ValveState = Field(..., description="Required state of the valve")
|
|
120
|
+
time: float | None = Field(None, description="Time in seconds that the valve must be in the required state before the transition is valid")
|
|
121
|
+
|
|
122
|
+
@model_validator(mode="before")
|
|
123
|
+
def str2model(cls, v):
|
|
124
|
+
if isinstance(v, str):
|
|
125
|
+
return {"state": v}
|
|
126
|
+
return v
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TransitionConditions(StrictModel):
|
|
130
|
+
gauge: dict[str, Inequality] = Field({}, description="A mapping of gauge names to pressure inequalities that must be satisfied for the transition to be valid")
|
|
131
|
+
pump: dict[str, PumpCondition] = Field({}, description="A mapping of pump names to conditions that must be satisfied for the transition to be valid")
|
|
132
|
+
valve: dict[str, ValveCondition] = Field({}, description="A mapping of valve names to conditions that must be satisfied for the transition to be valid")
|
|
133
|
+
stage: str | None = Field(None, description="Current stage position required for the transition to be valid")
|
|
134
|
+
|
|
135
|
+
def _check_valves(self, valves: list[str]):
|
|
136
|
+
if self.valve is not None:
|
|
137
|
+
for valve in self.valve.keys():
|
|
138
|
+
if valve not in valves:
|
|
139
|
+
raise ValueError(f"Valve {valve} not in valves list")
|
|
140
|
+
|
|
141
|
+
def _check_gauges(self, gauges: list[str]):
|
|
142
|
+
if self.gauge is not None:
|
|
143
|
+
for gauge in self.gauge.keys():
|
|
144
|
+
if gauge not in gauges:
|
|
145
|
+
raise ValueError(f"Gauge {gauge} not in gauges list")
|
|
146
|
+
|
|
147
|
+
def _check_pumps(self, pumps: list[str]):
|
|
148
|
+
if self.pump is not None:
|
|
149
|
+
for pump in self.pump.keys():
|
|
150
|
+
if pump not in pumps:
|
|
151
|
+
raise ValueError(f"Pump {pump} not in pumps list")
|
|
152
|
+
|
|
153
|
+
def _check_stage(self, stages: list[str]):
|
|
154
|
+
if self.stage is not None and self.stage not in stages:
|
|
155
|
+
raise ValueError(f"Stage position {self.stage} not in stage position list")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class Transition(StrictModel):
|
|
159
|
+
stage: bool = Field(False, description="Set to true if this transition is a stage movement")
|
|
160
|
+
valve: str | None = Field(None, description="Name of the valve to be actuated in this transition")
|
|
161
|
+
pump: str | None = Field(None, description="Name of the pump to be actuated in this transition")
|
|
162
|
+
from_state: ValveState | PumpState | str = Field(
|
|
163
|
+
..., validation_alias="from", serialization_alias="from", description="State of the valve, pump, or stage before the transition"
|
|
164
|
+
)
|
|
165
|
+
to_state: ValveState | PumpState | str = Field(
|
|
166
|
+
..., validation_alias="to", serialization_alias="to", description="State of the valve, pump, or stage after the transition"
|
|
167
|
+
)
|
|
168
|
+
condition: TransitionConditions | None = Field(None, description="Conditions that must be met for this transition to be valid")
|
|
169
|
+
wait: dict[str, Inequality] = Field({}, description="A mapping of gauge names to pressure inequalities that should be satisfied before this transition is executed when automated pumping is enabled")
|
|
170
|
+
safety: SafetyConditions | None = Field(None, description="Safety conditions under which this transition should be automatically executed to prevent damage to the system")
|
|
171
|
+
pressure: list[PressureChange] = Field([], description="A list of pressure changes that are expected if this transition is executed")
|
|
172
|
+
|
|
173
|
+
@model_validator(mode="before")
|
|
174
|
+
def pressure2list(cls, v):
|
|
175
|
+
if "pressure" in v and isinstance(v["pressure"], dict):
|
|
176
|
+
v["pressure"] = [v["pressure"]]
|
|
177
|
+
return v
|
|
178
|
+
|
|
179
|
+
@model_validator(mode="after")
|
|
180
|
+
def from_to_state2enum(self):
|
|
181
|
+
if self.valve is not None:
|
|
182
|
+
self.from_state = ValveState(self.from_state)
|
|
183
|
+
self.to_state = ValveState(self.to_state)
|
|
184
|
+
if self.pump is not None:
|
|
185
|
+
self.from_state = PumpState(self.from_state)
|
|
186
|
+
self.to_state = PumpState(self.to_state)
|
|
187
|
+
return self
|
|
188
|
+
|
|
189
|
+
def _check_valves(self, valves: list[str]):
|
|
190
|
+
if self.valve is not None and self.valve not in valves:
|
|
191
|
+
raise ValueError(f"Valve {self.valve} not in valves list")
|
|
192
|
+
if self.condition is not None:
|
|
193
|
+
self.condition._check_valves(valves)
|
|
194
|
+
|
|
195
|
+
def _check_gauges(self, gauges: list[str]):
|
|
196
|
+
if self.condition is not None:
|
|
197
|
+
self.condition._check_gauges(gauges)
|
|
198
|
+
if self.pressure is not None:
|
|
199
|
+
for pressure_change in self.pressure:
|
|
200
|
+
pressure_change._check_gauges(gauges)
|
|
201
|
+
if self.wait is not None:
|
|
202
|
+
for gauge in self.wait.keys():
|
|
203
|
+
if gauge not in gauges:
|
|
204
|
+
raise ValueError(f"Gauge {gauge} not in gauges list")
|
|
205
|
+
|
|
206
|
+
def _check_pumps(self, pumps: list[str]):
|
|
207
|
+
if self.pump is not None and self.pump not in pumps:
|
|
208
|
+
raise ValueError(f"Pump {self.pump} not in pumps list")
|
|
209
|
+
if self.safety is not None:
|
|
210
|
+
self.safety._check_pumps(pumps)
|
|
211
|
+
if self.condition is not None:
|
|
212
|
+
self.condition._check_pumps(pumps)
|
|
213
|
+
|
|
214
|
+
def _check_stage(self, stages: list[str]):
|
|
215
|
+
if self.condition is not None:
|
|
216
|
+
self.condition._check_stage(stages)
|
|
217
|
+
if self.stage:
|
|
218
|
+
if self.from_state not in stages:
|
|
219
|
+
raise ValueError(
|
|
220
|
+
f"Stage position {self.from_state} not in stage position list"
|
|
221
|
+
)
|
|
222
|
+
if self.to_state not in stages:
|
|
223
|
+
raise ValueError(
|
|
224
|
+
f"Stage position {self.to_state} not in stage position list"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class Confirm(StrictModel):
|
|
229
|
+
default: bool = Field(False, description="Default confirmation requirement for transitions")
|
|
230
|
+
pump: dict[str, PumpState | bool] | None = Field(None, description="Require confirmation before turning pumps on or off, or True to require confirmation for all transitions of a specific pump")
|
|
231
|
+
stage: list[str] | None = Field(None, description="Require confirmation before moving the stage to any of these positions")
|
|
232
|
+
valve: dict[str, ValveState | bool] | None = Field(None, description="Require confirmation before opening or closing valves, or True to require confirmation for all transitions of a specific valve")
|
|
233
|
+
|
|
234
|
+
def _check_valves(self, valves: list[str]):
|
|
235
|
+
if self.valve is not None:
|
|
236
|
+
for valve in self.valve.keys():
|
|
237
|
+
if valve not in valves:
|
|
238
|
+
raise ValueError(f"Valve {valve} not in valves list")
|
|
239
|
+
|
|
240
|
+
def _check_pumps(self, pumps: list[str]):
|
|
241
|
+
if self.pump is not None:
|
|
242
|
+
for pump in self.pump.keys():
|
|
243
|
+
if pump not in pumps:
|
|
244
|
+
raise ValueError(f"Pump {pump} not in pumps list")
|
|
245
|
+
|
|
246
|
+
def _check_stage(self, stages: list[str]):
|
|
247
|
+
if self.stage is not None:
|
|
248
|
+
for position in self.stage:
|
|
249
|
+
if position not in stages:
|
|
250
|
+
raise ValueError(
|
|
251
|
+
f"Stage position {position} not in stage position list"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class InitialState(StrictModel):
|
|
256
|
+
valves: dict[str, ValveState] | None = Field(None, description="Initial state of each valve")
|
|
257
|
+
pumps: dict[str, PumpState] | None = Field(None, description="Initial state of each pump")
|
|
258
|
+
stage: str | None = Field(None, description="Initial stage location")
|
|
259
|
+
gauges: dict[str, float] | None = Field(None, description="Initial pressure of each gauge")
|
|
260
|
+
|
|
261
|
+
def _check_valves(self, valves: list[str]):
|
|
262
|
+
for valve in valves:
|
|
263
|
+
if self.valves is None or valve not in self.valves.keys():
|
|
264
|
+
raise ValueError(f"Valve {valve} not in initial state")
|
|
265
|
+
|
|
266
|
+
def _check_gauges(self, gauges: list[str]):
|
|
267
|
+
for gauge in gauges:
|
|
268
|
+
if self.gauges is None or gauge not in self.gauges.keys():
|
|
269
|
+
raise ValueError(f"Gauge {gauge} not in initial state")
|
|
270
|
+
|
|
271
|
+
def _check_pumps(self, pumps: list[str]):
|
|
272
|
+
for pump in pumps:
|
|
273
|
+
if self.pumps is None or pump not in self.pumps.keys():
|
|
274
|
+
raise ValueError(f"Pump {pump} not in initial state")
|
|
275
|
+
|
|
276
|
+
def _check_stage(self, stages: list[str]):
|
|
277
|
+
if len(stages) > 0:
|
|
278
|
+
if self.stage is None:
|
|
279
|
+
raise ValueError(f"Stage position not in initial state")
|
|
280
|
+
if self.stage not in stages:
|
|
281
|
+
raise ValueError(
|
|
282
|
+
f"Initial stage position {self.stage} not in stage position list"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class Config(StrictModel):
|
|
287
|
+
valves: list[str] = Field(..., description="List of all valve names")
|
|
288
|
+
gauges: list[str] = Field(..., description="List of all gauge names")
|
|
289
|
+
pumps: list[str] = Field(..., description="List of all pump names")
|
|
290
|
+
stage: list[str] = Field(..., description="List of all stage positions")
|
|
291
|
+
named_states: dict[str, NamedState] = Field(..., description="A mapping of names of named states to their corresponding state definitions")
|
|
292
|
+
transitions: list[Transition] = Field(..., description="List of all possible transitions")
|
|
293
|
+
confirm: Confirm = Field(..., description="Confirmation requirements for transitions when automated pumping is enabled")
|
|
294
|
+
initial_states: list[InitialState] = Field(..., description="List of initial states to use when building the state graph")
|
|
295
|
+
|
|
296
|
+
@field_validator("initial_states")
|
|
297
|
+
def check_initial_states(cls, v):
|
|
298
|
+
if len(v) == 0:
|
|
299
|
+
raise ValueError("At least one initial state must be defined")
|
|
300
|
+
return v
|
|
301
|
+
|
|
302
|
+
@model_validator(mode="after")
|
|
303
|
+
def check_valves(self):
|
|
304
|
+
for transition in self.transitions:
|
|
305
|
+
transition._check_valves(self.valves)
|
|
306
|
+
self.confirm._check_valves(self.valves)
|
|
307
|
+
for state in self.named_states.values():
|
|
308
|
+
state._check_valves(self.valves)
|
|
309
|
+
for state in self.initial_states:
|
|
310
|
+
state._check_valves(self.valves)
|
|
311
|
+
return self
|
|
312
|
+
|
|
313
|
+
@model_validator(mode="after")
|
|
314
|
+
def check_gauges(self):
|
|
315
|
+
for transition in self.transitions:
|
|
316
|
+
transition._check_gauges(self.gauges)
|
|
317
|
+
for state in self.initial_states:
|
|
318
|
+
state._check_gauges(self.gauges)
|
|
319
|
+
return self
|
|
320
|
+
|
|
321
|
+
@model_validator(mode="after")
|
|
322
|
+
def check_pumps(self):
|
|
323
|
+
for transition in self.transitions:
|
|
324
|
+
transition._check_pumps(self.pumps)
|
|
325
|
+
self.confirm._check_pumps(self.pumps)
|
|
326
|
+
for state in self.named_states.values():
|
|
327
|
+
state._check_pumps(self.pumps)
|
|
328
|
+
for state in self.initial_states:
|
|
329
|
+
state._check_pumps(self.pumps)
|
|
330
|
+
return self
|
|
331
|
+
|
|
332
|
+
@model_validator(mode="after")
|
|
333
|
+
def check_stage(self):
|
|
334
|
+
for transition in self.transitions:
|
|
335
|
+
transition._check_stage(self.stage)
|
|
336
|
+
self.confirm._check_stage(self.stage)
|
|
337
|
+
for state in self.named_states.values():
|
|
338
|
+
state._check_stage(self.stage)
|
|
339
|
+
for state in self.initial_states:
|
|
340
|
+
state._check_stage(self.stage)
|
|
341
|
+
return self
|
|
342
|
+
|
|
343
|
+
@classmethod
|
|
344
|
+
def load_file(cls, path):
|
|
345
|
+
with open(path) as f:
|
|
346
|
+
return cls.load_yaml(f.read())
|
|
347
|
+
|
|
348
|
+
@classmethod
|
|
349
|
+
def load_yaml(cls, yaml_str):
|
|
350
|
+
data = yaml.safe_load(yaml_str)
|
|
351
|
+
return cls(**data)
|
auto_pumping/dummy.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from . import AutoPumping
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DummyPumping(AutoPumping):
|
|
6
|
+
def __init__(self, config):
|
|
7
|
+
self._valve_states = {valve: None for valve in config.valves}
|
|
8
|
+
self._pump_states = {pump: None for pump in config.pumps}
|
|
9
|
+
self._stage_position = None
|
|
10
|
+
super().__init__(config)
|
|
11
|
+
|
|
12
|
+
def actuate_valve(self, valve, state):
|
|
13
|
+
self._valve_states[valve] = state
|
|
14
|
+
|
|
15
|
+
def get_valve_state(self, valve):
|
|
16
|
+
return self._valve_states[valve]
|
|
17
|
+
|
|
18
|
+
def set_pump(self, pump, state):
|
|
19
|
+
self._pump_states[pump] = state
|
|
20
|
+
|
|
21
|
+
def get_pump_state(self, pump):
|
|
22
|
+
return self._pump_states[pump]
|
|
23
|
+
|
|
24
|
+
def get_pump_current(self, pump):
|
|
25
|
+
if pump == "turbo":
|
|
26
|
+
return random.uniform(0.0, 2.0)
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
def get_pump_voltage(self, pump):
|
|
30
|
+
if pump == "turbo":
|
|
31
|
+
return random.uniform(23.0, 25.0)
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
def get_pump_flow(self, pump):
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
def get_pump_speed(self, pump):
|
|
38
|
+
if pump == "turbo":
|
|
39
|
+
return random.uniform(998.0, 1002.0)
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
def get_stage_position(self):
|
|
43
|
+
return self._stage_position
|
|
44
|
+
|
|
45
|
+
def move_stage(self, position):
|
|
46
|
+
self._stage_position = position
|
|
47
|
+
|
|
48
|
+
def get_gauge_pressure(self, gauge):
|
|
49
|
+
return random.uniform(1e-6, 1e-5)
|