pychemstation 0.10.5__py3-none-any.whl → 0.10.6__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.
Files changed (27) hide show
  1. pychemstation/analysis/__init__.py +8 -1
  2. pychemstation/control/controllers/__init__.py +2 -2
  3. pychemstation/control/controllers/abc_tables/__init__.py +0 -0
  4. pychemstation/control/controllers/abc_tables/device.py +15 -0
  5. pychemstation/control/controllers/{tables/table.py → abc_tables/run.py} +24 -195
  6. pychemstation/control/controllers/abc_tables/table.py +221 -0
  7. pychemstation/control/controllers/comm.py +3 -99
  8. pychemstation/control/controllers/{tables → data_aq}/method.py +2 -2
  9. pychemstation/control/controllers/{tables → data_aq}/sequence.py +72 -53
  10. pychemstation/control/controllers/devices/__init__.py +3 -0
  11. pychemstation/control/controllers/devices/injector.py +57 -24
  12. pychemstation/control/hplc.py +5 -1
  13. pychemstation/utils/injector_types.py +22 -2
  14. pychemstation/utils/mocking/__init__.py +0 -0
  15. pychemstation/utils/mocking/abc_comm.py +160 -0
  16. pychemstation/utils/mocking/mock_comm.py +5 -0
  17. pychemstation/utils/mocking/mock_hplc.py +2 -0
  18. pychemstation/utils/sequence_types.py +19 -0
  19. pychemstation/utils/table_types.py +6 -0
  20. pychemstation/utils/tray_types.py +36 -1
  21. {pychemstation-0.10.5.dist-info → pychemstation-0.10.6.dist-info}/METADATA +1 -1
  22. pychemstation-0.10.6.dist-info/RECORD +42 -0
  23. pychemstation/control/controllers/devices/device.py +0 -74
  24. pychemstation-0.10.5.dist-info/RECORD +0 -36
  25. /pychemstation/control/controllers/{tables → data_aq}/__init__.py +0 -0
  26. {pychemstation-0.10.5.dist-info → pychemstation-0.10.6.dist-info}/WHEEL +0 -0
  27. {pychemstation-0.10.5.dist-info → pychemstation-0.10.6.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import time
3
3
  import warnings
4
- from typing import Dict, List, Optional, Any
4
+ from typing import Any, Dict, List, Optional, Union
5
5
 
6
6
  from result import Err, Ok, Result
7
7
  from typing_extensions import override
@@ -11,7 +11,6 @@ from pychemstation.analysis.chromatogram import (
11
11
  AgilentChannelChromatogramData,
12
12
  AgilentHPLCChromatogram,
13
13
  )
14
- from . import MethodController
15
14
 
16
15
  from ....analysis.process_report import AgilentReport, ReportType
17
16
  from ....control.controllers.comm import CommunicationController
@@ -25,10 +24,11 @@ from ....utils.sequence_types import (
25
24
  )
26
25
  from ....utils.table_types import RegisterFlag, T, Table
27
26
  from ....utils.tray_types import FiftyFourVialPlate, TenVialColumn, Tray
28
- from .table import TableController
27
+ from ..abc_tables.run import RunController
28
+ from . import MethodController
29
29
 
30
30
 
31
- class SequenceController(TableController):
31
+ class SequenceController(RunController):
32
32
  """
33
33
  Class containing sequence related logic
34
34
  """
@@ -146,13 +146,12 @@ class SequenceController(TableController):
146
146
  if rows.is_ok():
147
147
  existing_row_num = rows.ok_value.num_response
148
148
  wanted_row_num = len(sequence_table.rows)
149
- while existing_row_num != wanted_row_num:
150
- if wanted_row_num > existing_row_num:
151
- self.add_row()
152
- elif wanted_row_num < existing_row_num:
153
- self.delete_row(int(existing_row_num))
149
+ for i in range(int(existing_row_num)):
150
+ self.delete_row(int(existing_row_num - i))
151
+ self.send(Command.SAVE_SEQUENCE_CMD)
152
+ for i in range(int(wanted_row_num)):
153
+ self.add_row()
154
154
  self.send(Command.SAVE_SEQUENCE_CMD)
155
- existing_row_num = self.get_num_rows().ok_value.num_response
156
155
  self.send(Command.SWITCH_SEQUENCE_CMD)
157
156
 
158
157
  for i, row in enumerate(sequence_table.rows):
@@ -175,55 +174,68 @@ class SequenceController(TableController):
175
174
  self.send(Command.SAVE_SEQUENCE_CMD)
176
175
  num_rows = self.get_num_rows()
177
176
  if row.vial_location:
178
- loc = row.vial_location
179
- loc_num = -1
180
- if isinstance(loc, TenVialColumn):
181
- loc_num = loc.value
182
- elif isinstance(loc, FiftyFourVialPlate):
183
- loc_num = loc.value()
184
- self._edit_row_num(
185
- row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
186
- )
177
+ self.edit_vial_location(row.vial_location, row_num)
187
178
  if row.method:
188
- method_dir = self.method_controller.src
189
- possible_path = os.path.join(method_dir, row.method) + ".M\\"
190
- method = row.method
191
- if os.path.exists(possible_path):
192
- method = os.path.join(method_dir, row.method)
193
- self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
179
+ self.edit_method_name(row.method, row_num)
194
180
  if row.num_inj:
195
- self._edit_row_num(
196
- row=row_num, col_name=RegisterFlag.NUM_INJ, val=row.num_inj
197
- )
181
+ self.edit_num_injections(row.num_inj, row_num)
198
182
  if row.inj_vol:
199
- self._edit_row_text(
200
- row=row_num, col_name=RegisterFlag.INJ_VOL, val=str(row.inj_vol)
201
- )
183
+ self.edit_injection_volume(row.inj_vol, row_num)
202
184
  if row.inj_source:
203
- self._edit_row_text(
204
- row=row_num, col_name=RegisterFlag.INJ_SOR, val=row.inj_source.value
205
- )
185
+ self.edit_injection_source(row.inj_source, row_num)
206
186
  if row.sample_name:
207
- self._edit_row_text(
208
- row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name
209
- )
210
- if row.data_file:
211
- self._edit_row_text(
212
- row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.data_file
213
- )
214
- else:
215
- self._edit_row_text(
216
- row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name
217
- )
187
+ self.edit_sample_name(row.sample_name, row_num)
188
+ if row.data_file:
189
+ self.edit_data_file(row.data_file, row_num)
190
+ elif row.sample_name and not row.data_file:
191
+ self.edit_data_file(row.sample_name, row_num)
218
192
  if row.sample_type:
219
- self._edit_row_num(
220
- row=row_num,
221
- col_name=RegisterFlag.SAMPLE_TYPE,
222
- val=row.sample_type.value,
223
- )
224
-
193
+ self.edit_sample_type(row.sample_type, row_num)
225
194
  self.send(Command.SAVE_SEQUENCE_CMD)
226
195
 
196
+ def edit_sample_type(self, sample_type: SampleType, row_num: int):
197
+ self._edit_row_num(
198
+ row=row_num,
199
+ col_name=RegisterFlag.SAMPLE_TYPE,
200
+ val=sample_type.value,
201
+ )
202
+
203
+ def edit_data_file(self, data_file: str, row_num: int):
204
+ self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=data_file)
205
+
206
+ def edit_sample_name(self, sample_name: str, row_num: int):
207
+ self._edit_row_text(row=row_num, col_name=RegisterFlag.NAME, val=sample_name)
208
+
209
+ def edit_injection_source(self, inj_source: InjectionSource, row_num: int):
210
+ self._edit_row_text(
211
+ row=row_num, col_name=RegisterFlag.INJ_SOR, val=inj_source.value
212
+ )
213
+
214
+ def edit_injection_volume(self, inj_vol: Union[int, float], row_num: int):
215
+ self._edit_row_text(
216
+ row=row_num, col_name=RegisterFlag.INJ_VOL, val=str(inj_vol)
217
+ )
218
+
219
+ def edit_num_injections(self, num_inj: int, row_num: int):
220
+ self._edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=num_inj)
221
+
222
+ def edit_method_name(self, method: str, row_num: int):
223
+ method_dir = self.method_controller.src
224
+ possible_path = os.path.join(method_dir, method) + ".M\\"
225
+ if os.path.exists(possible_path):
226
+ method = os.path.join(method_dir, method)
227
+ self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
228
+
229
+ def edit_vial_location(self, loc: Tray, row_num: int):
230
+ loc_num = -1
231
+ if isinstance(loc, TenVialColumn):
232
+ loc_num = loc.value
233
+ elif isinstance(loc, FiftyFourVialPlate):
234
+ loc_num = loc.value()
235
+ self._edit_row_num(
236
+ row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
237
+ )
238
+
227
239
  def run(self, stall_while_running: bool = True):
228
240
  """
229
241
  Starts the currently loaded sequence, storing data
@@ -262,7 +274,14 @@ class SequenceController(TableController):
262
274
  self.send(Command.RUN_SEQUENCE_CMD.value)
263
275
  self.timeout = total_runtime * 60
264
276
 
265
- if self.check_hplc_is_running():
277
+ tries = 10
278
+ hplc_running = False
279
+ for _ in range(tries):
280
+ hplc_running = self.check_hplc_is_running()
281
+ if hplc_running:
282
+ break
283
+
284
+ if hplc_running:
266
285
  folder_name = f"{self.table_state.name} {timestamp}"
267
286
  data_file = SequenceDataFiles(
268
287
  dir=folder_name, sequence_name=self.table_state.name
@@ -276,7 +295,7 @@ class SequenceController(TableController):
276
295
  else:
277
296
  warnings.warn("Run may have not completed.")
278
297
  else:
279
- raise RuntimeError("Sequence run did not start.")
298
+ raise RuntimeError("Sequence run may not have started.")
280
299
 
281
300
  @override
282
301
  def fuzzy_match_most_recent_folder(
@@ -0,0 +1,3 @@
1
+ from .injector import InjectorController
2
+
3
+ __all__ = ["InjectorController"]
@@ -1,19 +1,21 @@
1
+ from ..abc_tables.device import DeviceController
1
2
  from ....control.controllers import CommunicationController
2
3
  from ....utils.injector_types import (
3
4
  Draw,
4
5
  Inject,
5
- InjectorFunction,
6
6
  InjectorTable,
7
7
  Mode,
8
8
  Remote,
9
9
  RemoteCommand,
10
10
  SourceType,
11
11
  Wait,
12
+ DrawDefault,
13
+ DrawDefaultVolume,
14
+ DrawDefaultLocation,
12
15
  )
13
16
  from ....utils.macro import Response
14
17
  from ....utils.table_types import RegisterFlag, Table
15
- from ....utils.tray_types import Tray
16
- from .device import DeviceController
18
+ from ....utils.tray_types import Tray, FiftyFourVialPlate, TenVialColumn, LocationPlus
17
19
 
18
20
 
19
21
  class InjectorController(DeviceController):
@@ -22,14 +24,32 @@ class InjectorController(DeviceController):
22
24
  ):
23
25
  super().__init__(controller, table, offline)
24
26
 
25
- def get_row(self, row: int) -> InjectorFunction:
26
- def return_tray_loc() -> Tray:
27
- raise NotImplementedError
28
- # unit = self.get_text(row, RegisterFlag.DRAW_LOCATION_UNIT)
29
- # tray = self.get_text(row, RegisterFlag.DRAW_LOCATION_TRAY)
30
- # x = self.get_text(row, RegisterFlag.DRAW_LOCATION_ROW)
31
- # y = self.get_text(row, RegisterFlag.DRAW_LOCATION_COLUMN)
32
- # return FiftyFourVialPlate.from_str("P1-A1")
27
+ def try_vial_location(self, val: str) -> Tray:
28
+ try:
29
+ return FiftyFourVialPlate.from_str(val)
30
+ except Exception:
31
+ try:
32
+ return TenVialColumn(int(val))
33
+ except Exception:
34
+ raise ValueError("Location could not be identified.")
35
+
36
+ def get_row(
37
+ self, row: int
38
+ ) -> (
39
+ Draw
40
+ | DrawDefaultVolume
41
+ | Inject
42
+ | Wait
43
+ | DrawDefault
44
+ | DrawDefaultLocation
45
+ | Remote
46
+ ):
47
+ def return_location_plus() -> Tray:
48
+ unit = self.get_num(row, RegisterFlag.DRAW_LOCATION_UNIT)
49
+ tray = self.get_num(row, RegisterFlag.DRAW_LOCATION_TRAY)
50
+ row_ = self.get_num(row, RegisterFlag.DRAW_LOCATION_ROW)
51
+ col = self.get_num(row, RegisterFlag.DRAW_LOCATION_COLUMN)
52
+ return LocationPlus(int(unit), int(tray), int(row_), int(col))
33
53
 
34
54
  function = self.get_text(row, RegisterFlag.FUNCTION)
35
55
  if function == "Wait":
@@ -37,20 +57,32 @@ class InjectorController(DeviceController):
37
57
  elif function == "Inject":
38
58
  return Inject()
39
59
  elif function == "Draw":
40
- # TODO: better error handling
41
60
  is_source = SourceType(self.get_text(row, RegisterFlag.DRAW_SOURCE))
42
61
  is_volume = Mode(self.get_text(row, RegisterFlag.DRAW_VOLUME))
43
- vol = (
44
- self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE)
45
- if is_volume == Mode.SET
46
- else None
47
- )
48
- if is_source is SourceType.SPECIFIC_LOCATION:
49
- return Draw(amount=vol, source=return_tray_loc())
50
- elif is_source is SourceType.LOCATION:
51
- return Draw(
52
- amount=vol, location=self.get_text(row, RegisterFlag.DRAW_LOCATION)
53
- )
62
+ if is_volume is not Mode.SET:
63
+ if is_source == SourceType.DEFAULT:
64
+ return DrawDefault()
65
+ elif is_source is SourceType.SPECIFIC_LOCATION:
66
+ return DrawDefaultVolume(location=return_location_plus())
67
+ elif is_source is SourceType.LOCATION:
68
+ return DrawDefaultVolume(
69
+ location=self.try_vial_location(
70
+ self.get_text(row, RegisterFlag.DRAW_LOCATION)
71
+ )
72
+ )
73
+ else:
74
+ vol = self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE)
75
+ if is_source == SourceType.DEFAULT:
76
+ return DrawDefaultLocation(amount=vol)
77
+ elif is_source is SourceType.SPECIFIC_LOCATION:
78
+ return Draw(amount=vol, location=return_location_plus())
79
+ elif is_source is SourceType.LOCATION:
80
+ return Draw(
81
+ amount=vol,
82
+ location=self.try_vial_location(
83
+ self.get_text(row, RegisterFlag.DRAW_LOCATION)
84
+ ),
85
+ )
54
86
  elif function == "Remote":
55
87
  return Remote(
56
88
  command=RemoteCommand(self.get_text(row, RegisterFlag.REMOTE)),
@@ -65,7 +97,8 @@ class InjectorController(DeviceController):
65
97
  if isinstance(row_response, Response):
66
98
  return InjectorTable(
67
99
  functions=[
68
- self.get_row(i) for i in range(int(row_response.num_response))
100
+ self.get_row(i + 1)
101
+ for i in range(int(row_response.num_response))
69
102
  ]
70
103
  )
71
104
  elif rows.is_err():
@@ -13,9 +13,10 @@ from pychemstation.analysis.chromatogram import (
13
13
  AgilentHPLCChromatogram,
14
14
  )
15
15
  from .controllers.devices.injector import InjectorController
16
- from .controllers.tables.sequence import SequenceController, MethodController
16
+ from .controllers.data_aq.sequence import SequenceController, MethodController
17
17
  from ..analysis.process_report import AgilentReport, ReportType
18
18
  from ..control.controllers import CommunicationController
19
+ from ..utils.injector_types import InjectorTable
19
20
  from ..utils.macro import Command, Response, Status
20
21
  from ..utils.method_types import MethodDetails
21
22
  from ..utils.sequence_types import SequenceTable
@@ -278,6 +279,9 @@ class HPLCController:
278
279
  """Returns the currently loaded sequence."""
279
280
  return self.sequence_controller.load()
280
281
 
282
+ def load_injector_program(self) -> InjectorTable:
283
+ return self.method_controller.injector_controller.load()
284
+
281
285
  def standby(self):
282
286
  """Switches all modules in standby mode. All lamps and pumps are switched off."""
283
287
  self.send(Command.STANDBY_CMD)
@@ -19,10 +19,25 @@ class Mode(Enum):
19
19
  @dataclass
20
20
  class Draw:
21
21
  amount: Optional[float] = None
22
- location: Optional[str] = None
22
+ location: Optional[Tray] = None
23
23
  source: Optional[Tray] = None
24
24
 
25
25
 
26
+ @dataclass
27
+ class DrawDefaultVolume:
28
+ location: Optional[Tray] = None
29
+
30
+
31
+ @dataclass
32
+ class DrawDefaultLocation:
33
+ amount: Optional[float] = None
34
+
35
+
36
+ @dataclass
37
+ class DrawDefault:
38
+ pass
39
+
40
+
26
41
  @dataclass
27
42
  class Wait:
28
43
  duration: Union[int, float]
@@ -35,6 +50,9 @@ class Inject:
35
50
 
36
51
  class RemoteCommand(Enum):
37
52
  START = "START"
53
+ NOT_READY = "NOT_READY"
54
+ STOP = "STOP"
55
+ READY = "READY"
38
56
  PREPARE = "PREPARE"
39
57
 
40
58
 
@@ -44,7 +62,9 @@ class Remote:
44
62
  duration: int
45
63
 
46
64
 
47
- InjectorFunction = Union[Draw, Wait, Inject, Remote]
65
+ InjectorFunction = Union[
66
+ Draw, DrawDefault, DrawDefaultVolume, DrawDefaultLocation, Wait, Inject, Remote
67
+ ]
48
68
 
49
69
 
50
70
  @dataclass
File without changes
@@ -0,0 +1,160 @@
1
+ """
2
+ Module to provide API for the communication with Agilent HPLC systems.
3
+
4
+ HPLCController sends commands to Chemstation software via a command file.
5
+ Answers are received via reply file. On the Chemstation side, a custom
6
+ Macro monitors the command file, executes commands and writes to the reply file.
7
+ Each command is given a number (cmd_no) to keep track of which commands have
8
+ been processed.
9
+
10
+ Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
11
+ """
12
+
13
+ import abc
14
+ import os
15
+ import time
16
+ from abc import abstractmethod
17
+ from typing import Union
18
+
19
+ from result import Err, Ok, Result
20
+
21
+ from ...utils.macro import (
22
+ HPLCAvailStatus,
23
+ Command,
24
+ Status,
25
+ Response,
26
+ )
27
+
28
+
29
+ class ABCCommunicationController(abc.ABC):
30
+ """
31
+ Abstract class representing the communication controller.
32
+ """
33
+
34
+ # maximum command number
35
+ MAX_CMD_NO = 255
36
+
37
+ def __init__(
38
+ self,
39
+ comm_dir: str,
40
+ cmd_file: str = "cmd",
41
+ reply_file: str = "reply",
42
+ offline: bool = False,
43
+ debug: bool = False,
44
+ ):
45
+ """
46
+ :param comm_dir:
47
+ :param cmd_file: Name of command file
48
+ :param reply_file: Name of reply file
49
+ :param debug: whether to save log of sent commands
50
+ """
51
+ if not offline:
52
+ self.debug = debug
53
+ if os.path.isdir(comm_dir):
54
+ self.cmd_file = os.path.join(comm_dir, cmd_file)
55
+ self.reply_file = os.path.join(comm_dir, reply_file)
56
+ self.cmd_no = 0
57
+ else:
58
+ raise FileNotFoundError(f"comm_dir: {comm_dir} not found.")
59
+
60
+ # Create files for Chemstation to communicate with Python
61
+ open(self.cmd_file, "a").close()
62
+ open(self.reply_file, "a").close()
63
+
64
+ self.reset_cmd_counter()
65
+ self._most_recent_hplc_status: Status = self.get_status()
66
+ self.send("Local Rows")
67
+
68
+ @abstractmethod
69
+ def get_num_val(self, cmd: str) -> Union[int, float]:
70
+ pass
71
+
72
+ @abstractmethod
73
+ def get_text_val(self, cmd: str) -> str:
74
+ pass
75
+
76
+ @abstractmethod
77
+ def get_status(self) -> Status:
78
+ pass
79
+
80
+ @abstractmethod
81
+ def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
82
+ pass
83
+
84
+ @abstractmethod
85
+ def _receive(self, cmd_no: int, num_attempts=100) -> Result[str, str]:
86
+ pass
87
+
88
+ def set_status(self):
89
+ """Updates current status of HPLC machine"""
90
+ self._most_recent_hplc_status = self.get_status()
91
+
92
+ def check_if_not_running(self) -> bool:
93
+ """Checks if HPLC machine is in an available state, meaning a state that data is not being written.
94
+
95
+ :return: whether the HPLC machine is in a safe state to retrieve data back."""
96
+ self.set_status()
97
+ hplc_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
98
+ time.sleep(5)
99
+ self.set_status()
100
+ hplc_actually_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
101
+ return hplc_avail and hplc_actually_avail
102
+
103
+ def sleepy_send(self, cmd: Union[Command, str]):
104
+ self.send("Sleep 0.1")
105
+ self.send(cmd)
106
+ self.send("Sleep 0.1")
107
+
108
+ def send(self, cmd: Union[Command, str]):
109
+ """Sends a command to Chemstation.
110
+
111
+ :param cmd: Command to be sent to HPLC
112
+ """
113
+ if self.cmd_no == self.MAX_CMD_NO:
114
+ self.reset_cmd_counter()
115
+
116
+ cmd_to_send: str = cmd.value if isinstance(cmd, Command) else cmd
117
+ self.cmd_no += 1
118
+ self._send(cmd_to_send, self.cmd_no)
119
+ if self.debug:
120
+ f = open("out.txt", "a")
121
+ f.write(cmd_to_send + "\n")
122
+ f.close()
123
+
124
+ def receive(self) -> Result[Response, str]:
125
+ """Returns messages received in reply file.
126
+
127
+ :return: ChemStation response
128
+ """
129
+ num_response_prefix = "Numerical Responses:"
130
+ str_response_prefix = "String Responses:"
131
+ possible_response = self._receive(self.cmd_no)
132
+ if possible_response.is_ok():
133
+ lines = possible_response.ok_value.splitlines()
134
+ for line in lines:
135
+ if str_response_prefix in line and num_response_prefix in line:
136
+ string_responses_dirty, _, numerical_responses = line.partition(
137
+ num_response_prefix
138
+ )
139
+ _, _, string_responses = string_responses_dirty.partition(
140
+ str_response_prefix
141
+ )
142
+ return Ok(
143
+ Response(
144
+ string_response=string_responses.strip(),
145
+ num_response=float(numerical_responses.strip()),
146
+ )
147
+ )
148
+ return Err("Could not retrieve HPLC response")
149
+ else:
150
+ return Err(f"Could not establish response to HPLC: {possible_response}")
151
+
152
+ def reset_cmd_counter(self):
153
+ """Resets the command counter."""
154
+ self._send(Command.RESET_COUNTER_CMD.value, cmd_no=self.MAX_CMD_NO + 1)
155
+ self._receive(cmd_no=self.MAX_CMD_NO + 1)
156
+ self.cmd_no = 0
157
+
158
+ def stop_macro(self):
159
+ """Stops Macro execution. Connection will be lost."""
160
+ self.send(Command.STOP_MACRO_CMD)
@@ -0,0 +1,5 @@
1
+ from pychemstation.utils.mocking.abc_comm import ABCCommunicationController
2
+
3
+
4
+ class MockCommunicationController(ABCCommunicationController):
5
+ pass
@@ -0,0 +1,2 @@
1
+ class MockHPLC:
2
+ pass
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os.path
3
4
  from enum import Enum
4
5
  from typing import Optional, List
5
6
  from dataclasses import dataclass, field
@@ -51,3 +52,21 @@ class SequenceEntry:
51
52
  class SequenceTable:
52
53
  name: str
53
54
  rows: list[SequenceEntry]
55
+
56
+ def __eq__(self, other):
57
+ equal = False
58
+ if not isinstance(other, SequenceTable):
59
+ return False
60
+
61
+ for self_row, other_row in zip(self.rows, other.rows):
62
+ equal |= self_row.vial_location == other_row.vial_location
63
+ equal |= self_row.data_file == other_row.data_file
64
+ equal |= (
65
+ os.path.split(os.path.normpath(self_row.method))[-1]
66
+ == os.path.split(os.path.normpath(other_row.method))[-1]
67
+ )
68
+ equal |= self_row.num_inj == other_row.num_inj
69
+ equal |= self_row.inj_vol == other_row.inj_vol
70
+ equal |= self_row.inj_source == other_row.inj_source
71
+ equal |= self_row.sample_name == other_row.sample_name
72
+ equal |= self_row.sample_type == other_row.sample_type
@@ -43,6 +43,12 @@ class RegisterFlag(Enum):
43
43
  FLOW = "Flow"
44
44
  MAX_TIME = "StopTime_Time"
45
45
  POST_TIME = "PostTime_Time" # TODO: check
46
+ SIGNAL_A = "Signal_Wavelength"
47
+ SIGNAL_B = "Signal2_Wavelength"
48
+ SIGNAL_C = "Signal3_Wavelength"
49
+ SIGNAL_D = "Signal4_Wavelength"
50
+ SIGNAL_E = "Signal5_Wavelength"
51
+ SIGNAL_A_USED = "Signal1_IsUsed"
46
52
  COLUMN_OVEN_TEMP1 = "TemperatureControl_Temperature"
47
53
  COLUMN_OVEN_TEMP2 = "TemperatureControl2_Temperature"
48
54
  STOPTIME_MODE = "StopTime_Mode"
@@ -71,6 +71,22 @@ class Letter(Enum):
71
71
  else:
72
72
  raise ValueError("Letter must be one of A to F")
73
73
 
74
+ @classmethod
75
+ def from_int(cls, num: int) -> Letter:
76
+ letter_mapping = {
77
+ "A": Letter.A,
78
+ "B": Letter.B,
79
+ "C": Letter.C,
80
+ "D": Letter.D,
81
+ "E": Letter.E,
82
+ "F": Letter.F,
83
+ }
84
+
85
+ if num <= len(letter_mapping):
86
+ return list(letter_mapping.values())[num]
87
+ else:
88
+ raise ValueError("Letter must be one of A to F")
89
+
74
90
 
75
91
  @dataclass
76
92
  class FiftyFourVialPlate:
@@ -91,6 +107,17 @@ class FiftyFourVialPlate:
91
107
  def value(self) -> int:
92
108
  return self.plate.value + self.letter.value + self.num.value
93
109
 
110
+ @classmethod
111
+ def from_tray_row_col(cls, tray: int, row: int, col: int):
112
+ try:
113
+ return FiftyFourVialPlate(
114
+ plate=Plate.from_num(tray),
115
+ letter=Letter.from_int(row),
116
+ num=Num.from_num(col),
117
+ )
118
+ except Exception:
119
+ raise ValueError("Could not parse tray location.")
120
+
94
121
  @classmethod
95
122
  def from_str(cls, loc: str):
96
123
  """
@@ -190,4 +217,12 @@ class TenVialColumn(Enum):
190
217
  TEN = 10
191
218
 
192
219
 
193
- Tray = Union[FiftyFourVialPlate, TenVialColumn]
220
+ @dataclass
221
+ class LocationPlus:
222
+ unit: int
223
+ tray: int
224
+ row: int
225
+ col: int
226
+
227
+
228
+ Tray = Union[FiftyFourVialPlate, TenVialColumn, LocationPlus]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pychemstation
3
- Version: 0.10.5
3
+ Version: 0.10.6
4
4
  Summary: Library to interact with Chemstation software, primarily used in Hein lab
5
5
  Project-URL: Documentation, https://pychemstation-e5a086.gitlab.io/pychemstation.html
6
6
  Project-URL: Repository, https://gitlab.com/heingroup/device-api/pychemstation