pychemstation 0.5.9__tar.gz → 0.5.10__tar.gz

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 (42) hide show
  1. {pychemstation-0.5.9 → pychemstation-0.5.10}/PKG-INFO +1 -1
  2. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/control/controllers/method.py +130 -107
  3. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/control/controllers/sequence.py +15 -48
  4. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/control/controllers/table_controller.py +56 -6
  5. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/control/hplc.py +30 -44
  6. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/utils/macro.py +6 -0
  7. pychemstation-0.5.10/pychemstation/utils/method_types.py +52 -0
  8. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/utils/sequence_types.py +2 -2
  9. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/utils/table_types.py +3 -0
  10. pychemstation-0.5.10/pychemstation/utils/tray_types.py +50 -0
  11. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation.egg-info/PKG-INFO +1 -1
  12. {pychemstation-0.5.9 → pychemstation-0.5.10}/setup.py +1 -1
  13. {pychemstation-0.5.9 → pychemstation-0.5.10}/tests/constants.py +15 -16
  14. {pychemstation-0.5.9 → pychemstation-0.5.10}/tests/test_comb.py +37 -44
  15. {pychemstation-0.5.9 → pychemstation-0.5.10}/tests/test_comm.py +1 -14
  16. {pychemstation-0.5.9 → pychemstation-0.5.10}/tests/test_method.py +7 -7
  17. {pychemstation-0.5.9 → pychemstation-0.5.10}/tests/test_sequence.py +7 -7
  18. pychemstation-0.5.9/pychemstation/utils/method_types.py +0 -43
  19. pychemstation-0.5.9/pychemstation/utils/tray_types.py +0 -14
  20. {pychemstation-0.5.9 → pychemstation-0.5.10}/LICENSE +0 -0
  21. {pychemstation-0.5.9 → pychemstation-0.5.10}/README.md +0 -0
  22. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/__init__.py +0 -0
  23. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/analysis/__init__.py +0 -0
  24. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/analysis/base_spectrum.py +0 -0
  25. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/analysis/spec_utils.py +0 -0
  26. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/analysis/utils.py +0 -0
  27. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/control/__init__.py +0 -0
  28. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/control/controllers/__init__.py +0 -0
  29. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/control/controllers/comm.py +0 -0
  30. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/generated/__init__.py +0 -0
  31. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/generated/dad_method.py +0 -0
  32. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/generated/pump_method.py +0 -0
  33. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/utils/__init__.py +0 -0
  34. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/utils/chromatogram.py +0 -0
  35. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation/utils/parsing.py +0 -0
  36. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation.egg-info/SOURCES.txt +0 -0
  37. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation.egg-info/dependency_links.txt +0 -0
  38. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation.egg-info/requires.txt +0 -0
  39. {pychemstation-0.5.9 → pychemstation-0.5.10}/pychemstation.egg-info/top_level.txt +0 -0
  40. {pychemstation-0.5.9 → pychemstation-0.5.10}/pyproject.toml +0 -0
  41. {pychemstation-0.5.9 → pychemstation-0.5.10}/setup.cfg +0 -0
  42. {pychemstation-0.5.9 → pychemstation-0.5.10}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pychemstation
3
- Version: 0.5.9
3
+ Version: 0.5.10
4
4
  Summary: Library to interact with Chemstation software, primarily used in Hein lab
5
5
  Home-page: https://gitlab.com/heingroup/device-api/pychemstation
6
6
  Author: Lucy Hao
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  import time
3
- from typing import Optional
3
+ from typing import Optional, Union
4
4
 
5
5
  from xsdata.formats.dataclass.parsers import XmlParser
6
6
 
@@ -9,7 +9,7 @@ from ...control.controllers.comm import CommunicationController
9
9
  from ...generated import PumpMethod, DadMethod, SolventElement
10
10
  from ...utils.chromatogram import TIME_FORMAT, AgilentChannelChromatogramData
11
11
  from ...utils.macro import Command
12
- from ...utils.method_types import PType, TimeTableEntry, Param, MethodTimetable, HPLCMethodParams
12
+ from ...utils.method_types import PType, TimeTableEntry, Param, MethodDetails, HPLCMethodParams
13
13
  from ...utils.table_types import RegisterFlag, TableOperation, Table
14
14
 
15
15
 
@@ -43,30 +43,53 @@ class MethodController(TableController):
43
43
  register_flag=RegisterFlag.FLOW
44
44
  )
45
45
  ),
46
- maximum_run_time=self.controller.get_num_val(
47
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
48
- register=self.table.register,
49
- register_flag=RegisterFlag.MAX_TIME
50
- )
51
- ),
52
46
  )
53
47
 
54
48
  def get_row(self, row: int) -> TimeTableEntry:
55
49
  return TimeTableEntry(start_time=self.get_num(row, RegisterFlag.TIME),
56
50
  organic_modifer=self.get_num(row, RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION),
57
- flow=None)
58
-
59
- def load(self) -> MethodTimetable:
51
+ flow=self.get_num(row, RegisterFlag.TIMETABLE_FLOW))
52
+
53
+ def get_timetable(self, rows: int):
54
+ uncoalesced_timetable_rows = [self.get_row(r + 1) for r in range(rows)]
55
+ timetable_rows = {}
56
+ for row in uncoalesced_timetable_rows:
57
+ time_key = str(row.start_time)
58
+ if time_key not in timetable_rows.keys():
59
+ timetable_rows[time_key] = TimeTableEntry(start_time=row.start_time,
60
+ flow=row.flow,
61
+ organic_modifer=row.organic_modifer)
62
+ else:
63
+ if row.flow:
64
+ timetable_rows[time_key].flow = row.flow
65
+ if row.organic_modifer:
66
+ timetable_rows[time_key].organic_modifer = row.organic_modifer
67
+ entries = list(timetable_rows.values())
68
+ entries.sort(key=lambda e: e.start_time)
69
+ return entries
70
+
71
+ def load(self) -> MethodDetails:
60
72
  rows = self.get_num_rows()
61
73
  if rows.is_ok():
62
74
  self.send(Command.GET_METHOD_CMD)
63
75
  res = self.receive()
64
76
  method_name = res.ok_value.string_response
65
- timetable_rows = [self.get_row(r + 1) for r in range(int(rows.ok_value.num_response))]
66
- self.table_state = MethodTimetable(
77
+ timetable_rows = self.get_timetable(int(rows.ok_value.num_response))
78
+ params = self.get_method_params()
79
+ stop_time = self.controller.get_num_val(
80
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
81
+ register=self.table.register,
82
+ register_flag=RegisterFlag.MAX_TIME))
83
+ post_time = self.controller.get_num_val(
84
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
85
+ register=self.table.register,
86
+ register_flag=RegisterFlag.POST_TIME))
87
+ self.table_state = MethodDetails(
67
88
  name=method_name,
68
- first_row=self.get_method_params(),
69
- subsequent_rows=timetable_rows)
89
+ timetable=timetable_rows,
90
+ stop_time=stop_time,
91
+ post_time=post_time,
92
+ params=params)
70
93
  return self.table_state
71
94
  else:
72
95
  raise RuntimeError(rows.err_value)
@@ -104,7 +127,7 @@ class MethodController(TableController):
104
127
  assert parsed_response == f"{method_name}.M", "Switching Methods failed."
105
128
  self.table_state = None
106
129
 
107
- def load_from_disk(self, method_name: str) -> MethodTimetable:
130
+ def load_from_disk(self, method_name: str) -> MethodDetails:
108
131
  """
109
132
  Retrieve method details of an existing method. Don't need to append ".M" to the end. This assumes the
110
133
  organic modifier is in Channel B and that Channel A contains the aq layer. Additionally, assumes
@@ -133,120 +156,69 @@ class MethodController(TableController):
133
156
  elif solvent.channel == "Channel_B":
134
157
  organic_modifier = solvent
135
158
 
136
- self.table_state = MethodTimetable(
137
- first_row=HPLCMethodParams(
138
- organic_modifier=organic_modifier.percentage,
139
- flow=method.flow,
140
- maximum_run_time=method.stop_time.stop_time_value),
141
- subsequent_rows=[
142
- TimeTableEntry(
143
- start_time=tte.time,
144
- organic_modifer=tte.percent_b,
145
- flow=method.flow
146
- ) for tte in method.timetable.timetable_entry
147
- ],
148
- dad_wavelengthes=dad.signals.signal,
149
- organic_modifier=organic_modifier,
150
- modifier_a=aq_modifier
151
- )
159
+ self.table_state = MethodDetails(
160
+ name=method_name,
161
+ params=HPLCMethodParams(organic_modifier=organic_modifier.percentage,
162
+ flow=method.flow),
163
+ stop_time=method.stop_time.stop_time_value,
164
+ post_time=method.post_time.post_time_value,
165
+ timetable=[TimeTableEntry(start_time=tte.time,
166
+ organic_modifer=tte.percent_b,
167
+ flow=method.flow
168
+ ) for tte in method.timetable.timetable_entry],
169
+ dad_wavelengthes=dad.signals.signal)
152
170
  return self.table_state
153
171
  else:
154
172
  raise FileNotFoundError
155
173
 
156
- def edit(self, updated_method: MethodTimetable, save: bool):
174
+ def edit(self, updated_method: MethodDetails, save: bool):
157
175
  """Updated the currently loaded method in ChemStation with provided values.
158
176
 
159
177
  :param updated_method: the method with updated values, to be sent to Chemstation to modify the currently loaded method.
178
+ :param save: if false only modifies the method, otherwise saves to disk
160
179
  """
161
180
  self.table_state = updated_method
162
- initial_organic_modifier: Param = Param(val=updated_method.first_row.organic_modifier,
181
+ initial_organic_modifier: Param = Param(val=updated_method.params.organic_modifier,
163
182
  chemstation_key=RegisterFlag.SOLVENT_B_COMPOSITION,
164
183
  ptype=PType.NUM)
165
- max_time: Param = Param(val=updated_method.first_row.maximum_run_time,
184
+ max_time: Param = Param(val=updated_method.stop_time,
166
185
  chemstation_key=RegisterFlag.MAX_TIME,
167
186
  ptype=PType.NUM)
168
- flow: Param = Param(val=updated_method.first_row.flow,
187
+ post_time: Param = Param(val=updated_method.post_time,
188
+ chemstation_key=RegisterFlag.POST_TIME,
189
+ ptype=PType.NUM) # TODO check postime works
190
+ flow: Param = Param(val=updated_method.params.flow,
169
191
  chemstation_key=RegisterFlag.FLOW,
170
192
  ptype=PType.NUM)
171
193
 
172
194
  # Method settings required for all runs
173
- self.delete_table()
174
- self._update_param(initial_organic_modifier)
175
- self._update_param(flow)
176
- self._update_param(Param(val="Set",
177
- chemstation_key=RegisterFlag.STOPTIME_MODE,
178
- ptype=PType.STR))
179
- self._update_param(max_time)
180
- self._update_param(Param(val="Off",
181
- chemstation_key=RegisterFlag.POSTIME_MODE,
182
- ptype=PType.STR))
183
-
184
- self.send("DownloadRCMethod PMP1")
185
-
186
- self._update_method_timetable(updated_method.subsequent_rows)
195
+ self.update_method_params(flow, initial_organic_modifier, max_time, post_time)
196
+ self._update_method_timetable(updated_method.timetable)
187
197
 
188
198
  if save:
189
199
  self.send(Command.SAVE_METHOD_CMD.value.format(
190
200
  commit_msg=f"saved method at {str(time.time())}"
191
201
  ))
192
202
 
193
- def _update_method_timetable(self, timetable_rows: list[TimeTableEntry]):
194
- self.sleepy_send('Local Rows')
195
- self.get_num_rows()
196
-
197
- self.sleepy_send('DelTab RCPMP1Method[1], "Timetable"')
198
- res = self.get_num_rows()
199
- while not res.is_err():
200
- self.sleepy_send('DelTab RCPMP1Method[1], "Timetable"')
201
- res = self.get_num_rows()
202
-
203
- self.sleepy_send('NewTab RCPMP1Method[1], "Timetable"')
204
- self.get_num_rows()
205
-
206
- for i, row in enumerate(timetable_rows):
207
- if i == 0:
208
- self.send('Sleep 1')
209
- self.sleepy_send('InsTabRow RCPMP1Method[1], "Timetable"')
210
- self.send('Sleep 1')
211
-
212
- self.sleepy_send('NewColText RCPMP1Method[1], "Timetable", "Function", "SolventComposition"')
213
- self.sleepy_send(f'NewColVal RCPMP1Method[1], "Timetable", "Time", {row.start_time}')
214
- self.sleepy_send(
215
- f'NewColVal RCPMP1Method[1], "Timetable", "SolventCompositionPumpChannel2_Percentage", {row.organic_modifer}')
216
-
217
- self.sleepy_send('InsTabRow RCPMP1Method[1], "Timetable"')
218
- self.sleepy_send('SetTabText RCPMP1Method[1], "Timetable", 2, "Function", "Flow"')
219
- self.sleepy_send(f'NewColVal RCPMP1Method[1], "Timetable", "FlowFlow", {row.flow}')
220
- self.sleepy_send(f'SetTabVal RCPMP1Method[1], "Timetable", 2, "FlowFlow", {row.flow})')
221
-
222
- self.send('Sleep 1')
223
- self.sleepy_send("DownloadRCMethod PMP1")
224
- self.send('Sleep 1')
225
- else:
226
- self.sleepy_send('InsTabRow RCPMP1Method[1], "Timetable"')
227
- self.get_num_rows()
228
-
229
- self.sleepy_send(
230
- f'SetTabText RCPMP1Method[1], "Timetable", Rows, "Function", "SolventComposition"')
231
- self.sleepy_send(
232
- f'SetTabVal RCPMP1Method[1], "Timetable", Rows, "Time", {row.start_time}')
233
- self.sleepy_send(
234
- f'SetTabVal RCPMP1Method[1], "Timetable", Rows, "SolventCompositionPumpChannel2_Percentage", {row.organic_modifer}')
235
-
236
- self.send("Sleep 1")
237
- self.sleepy_send("DownloadRCMethod PMP1")
238
- self.send("Sleep 1")
239
-
240
- self.sleepy_send('InsTabRow RCPMP1Method[1], "Timetable"')
241
- self.get_num_rows()
242
-
243
- self.sleepy_send('SetTabText RCPMP1Method[1], "Timetable", Rows , "Function", "Flow"')
244
- self.sleepy_send(f'SetTabVal RCPMP1Method[1], "Timetable", Rows , "FlowFlow", {row.flow}')
245
- self.sleepy_send(f'SetTabVal RCPMP1Method[1], "Timetable", Rows , "Time", {row.start_time}')
246
-
247
- self.send("Sleep 1")
248
- self.sleepy_send("DownloadRCMethod PMP1")
249
- self.send("Sleep 1")
203
+ def update_method_params(self, flow, initial_organic_modifier, max_time, post_time):
204
+ self.delete_table()
205
+ self._update_param(initial_organic_modifier)
206
+ self._update_param(flow)
207
+ if self.table_state.stop_time:
208
+ self._update_param(Param(val="Set",
209
+ chemstation_key=RegisterFlag.STOPTIME_MODE,
210
+ ptype=PType.STR))
211
+ self._update_param(max_time)
212
+ else:
213
+ self._update_param(Param(val="Off",
214
+ chemstation_key=RegisterFlag.STOPTIME_MODE,
215
+ ptype=PType.STR))
216
+ if self.table_state.post_time:
217
+ self._update_param(Param(val="Set",
218
+ chemstation_key=RegisterFlag.POSTIME_MODE,
219
+ ptype=PType.STR))
220
+ self._update_param(post_time)
221
+ self.download()
250
222
 
251
223
  def _update_param(self, method_param: Param):
252
224
  """Change a method parameter, changes what is visibly seen in Chemstation GUI.
@@ -267,6 +239,57 @@ class MethodController(TableController):
267
239
  val=method_param.val))
268
240
  time.sleep(2)
269
241
 
242
+ def download(self):
243
+ self.send('Sleep 1')
244
+ self.sleepy_send("DownloadRCMethod PMP1")
245
+ self.send('Sleep 1')
246
+
247
+ def edit_row(self, row: TimeTableEntry, first_row: bool = False):
248
+ if first_row:
249
+ self.add_row()
250
+ self.add_new_col_text(col_name=RegisterFlag.FUNCTION,
251
+ val=RegisterFlag.SOLVENT_COMPOSITION.value)
252
+ self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
253
+ self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
254
+ val=row.organic_modifer)
255
+ self.add_row()
256
+ self.get_num_rows()
257
+ self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value)
258
+ self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
259
+ self.edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
260
+ self.download()
261
+ else:
262
+ self.add_row()
263
+ self.get_num_rows()
264
+ self.edit_row_text(col_name=RegisterFlag.FUNCTION,
265
+ val=RegisterFlag.SOLVENT_COMPOSITION.value)
266
+ self.edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
267
+ self.edit_row_num(col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
268
+ val=row.organic_modifer)
269
+ self.download()
270
+
271
+ self.add_row()
272
+ self.get_num_rows()
273
+ self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value)
274
+ self.edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
275
+ self.edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
276
+ self.download()
277
+
278
+ def _update_method_timetable(self, timetable_rows: list[TimeTableEntry]):
279
+ self.get_num_rows()
280
+
281
+ self.delete_table()
282
+ res = self.get_num_rows()
283
+ while not res.is_err():
284
+ self.delete_table()
285
+ res = self.get_num_rows()
286
+
287
+ self.new_table()
288
+ self.get_num_rows()
289
+
290
+ for i, row in enumerate(timetable_rows):
291
+ self.edit_row(row=row, first_row=i == 0)
292
+
270
293
  def stop(self):
271
294
  """
272
295
  Stops the method run. A dialog window will pop up and manual intervention may be required.\
@@ -1,5 +1,4 @@
1
- from typing import Optional
2
-
1
+ from typing import Optional
3
2
 
4
3
  import os
5
4
  import time
@@ -10,6 +9,7 @@ from ...utils.chromatogram import SEQUENCE_TIME_FORMAT, AgilentChannelChromatogr
10
9
  from ...utils.macro import Command
11
10
  from ...utils.sequence_types import SequenceTable, SequenceEntry, SequenceDataFiles, InjectionSource, SampleType
12
11
  from ...utils.table_types import TableOperation, RegisterFlag, Table
12
+ from ...utils.tray_types import TenColumn
13
13
 
14
14
 
15
15
  class SequenceController(TableController):
@@ -47,7 +47,7 @@ class SequenceController(TableController):
47
47
  num_inj=num_inj,
48
48
  inj_vol=inj_vol,
49
49
  inj_source=inj_source,
50
- sample_type=sample_type, )
50
+ sample_type=sample_type)
51
51
 
52
52
  def check(self) -> str:
53
53
  time.sleep(2)
@@ -116,67 +116,34 @@ class SequenceController(TableController):
116
116
  self.send(Command.SAVE_SEQUENCE_CMD)
117
117
  num_rows = self.get_num_rows()
118
118
 
119
- table_register = self.table.register
120
- table_name = self.table.name
121
-
122
119
  if row.vial_location:
123
120
  loc = row.vial_location
124
- if isinstance(row.vial_location, InjectionSource):
121
+ if isinstance(row.vial_location, TenColumn):
125
122
  loc = row.vial_location.value
126
- self.sleepy_send(TableOperation.EDIT_ROW_VAL.value.format(register=table_register,
127
- table_name=table_name,
128
- row=row_num,
129
- col_name=RegisterFlag.VIAL_LOCATION,
130
- val=loc))
123
+ self.edit_row_num(row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc)
124
+
131
125
  if row.method:
132
126
  possible_path = os.path.join(self.method_dir, row.method) + ".M\\"
133
127
  method = row.method
134
128
  if os.path.exists(possible_path):
135
129
  method = os.path.join(self.method_dir, row.method)
136
- self.sleepy_send(TableOperation.EDIT_ROW_TEXT.value.format(register=table_register,
137
- table_name=table_name,
138
- row=row_num,
139
- col_name=RegisterFlag.METHOD,
140
- val=method))
130
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
141
131
 
142
132
  if row.num_inj:
143
- self.sleepy_send(TableOperation.EDIT_ROW_VAL.value.format(register=table_register,
144
- table_name=table_name,
145
- row=row_num,
146
- col_name=RegisterFlag.NUM_INJ,
147
- val=row.num_inj))
133
+ self.edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=row.num_inj)
148
134
 
149
135
  if row.inj_vol:
150
- self.sleepy_send(TableOperation.EDIT_ROW_TEXT.value.format(register=table_register,
151
- table_name=table_name,
152
- row=row_num,
153
- col_name=RegisterFlag.INJ_VOL,
154
- val=row.inj_vol))
136
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.INJ_VOL, val=row.inj_vol)
155
137
 
156
138
  if row.inj_source:
157
- self.sleepy_send(TableOperation.EDIT_ROW_TEXT.value.format(register=table_register,
158
- table_name=table_name,
159
- row=row_num,
160
- col_name=RegisterFlag.INJ_SOR,
161
- val=row.inj_source.value))
139
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.INJ_SOR, val=row.inj_source.value)
162
140
 
163
141
  if row.sample_name:
164
- self.sleepy_send(TableOperation.EDIT_ROW_TEXT.value.format(register=table_register,
165
- table_name=table_name,
166
- row=row_num,
167
- col_name=RegisterFlag.NAME,
168
- val=row.sample_name))
169
- self.sleepy_send(TableOperation.EDIT_ROW_TEXT.value.format(register=table_register,
170
- table_name=table_name,
171
- row=row_num,
172
- col_name=RegisterFlag.DATA_FILE,
173
- val=row.sample_name))
142
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name)
143
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name)
144
+
174
145
  if row.sample_type:
175
- self.sleepy_send(TableOperation.EDIT_ROW_VAL.value.format(register=table_register,
176
- table_name=table_name,
177
- row=row_num,
178
- col_name=RegisterFlag.SAMPLE_TYPE,
179
- val=row.sample_type.value))
146
+ self.edit_row_num(row=row_num, col_name=RegisterFlag.SAMPLE_TYPE, val=row.sample_type.value)
180
147
 
181
148
  self.send(Command.SAVE_SEQUENCE_CMD)
182
149
 
@@ -210,7 +177,7 @@ class SequenceController(TableController):
210
177
  sequence_data_files: SequenceDataFiles = self.data_files[-1]
211
178
  return sequence_data_files.dir
212
179
 
213
- def get_data(self, custom_path:Optional[str] = None ) -> list[AgilentChannelChromatogramData]:
180
+ def get_data(self, custom_path: Optional[str] = None) -> list[AgilentChannelChromatogramData]:
214
181
  parent_dir = self.data_files[-1].dir if not custom_path else custom_path
215
182
  subdirs = [x[0] for x in os.walk(self.data_dir)]
216
183
  potential_folders = sorted(list(filter(lambda d: parent_dir in d, subdirs)))
@@ -14,19 +14,19 @@ from result import Result, Ok, Err
14
14
  from ...control.controllers.comm import CommunicationController
15
15
  from ...utils.chromatogram import AgilentHPLCChromatogram, AgilentChannelChromatogramData
16
16
  from ...utils.macro import Command, HPLCRunningStatus, Response
17
- from ...utils.method_types import MethodTimetable
17
+ from ...utils.method_types import MethodDetails
18
18
  from ...utils.sequence_types import SequenceDataFiles, SequenceTable
19
19
  from ...utils.table_types import Table, TableOperation, RegisterFlag
20
20
 
21
+ TableType = Union[MethodDetails, SequenceTable]
21
22
 
22
- TableType = Union[MethodTimetable, SequenceTable]
23
23
 
24
24
  class TableController(abc.ABC):
25
25
 
26
26
  def __init__(self, controller: CommunicationController, src: str, data_dir: str, table: Table):
27
27
  self.controller = controller
28
28
  self.table = table
29
- self.table_state : Optional[TableType] = None
29
+ self.table_state: Optional[TableType] = None
30
30
 
31
31
  if os.path.isdir(src):
32
32
  self.src: str = src
@@ -38,7 +38,7 @@ class TableController(abc.ABC):
38
38
  else:
39
39
  raise FileNotFoundError(f"dir: {data_dir} not found.")
40
40
 
41
- self.spectra: dict[str, AgilentHPLCChromatogram] = {
41
+ self.spectra: dict[str, Optional[AgilentHPLCChromatogram]] = {
42
42
  "A": AgilentHPLCChromatogram(self.data_dir),
43
43
  "B": AgilentHPLCChromatogram(self.data_dir),
44
44
  "C": AgilentHPLCChromatogram(self.data_dir),
@@ -51,6 +51,9 @@ class TableController(abc.ABC):
51
51
 
52
52
  self.data_files: Union[list[SequenceDataFiles], list[str]] = []
53
53
 
54
+ # Initialize row counter for table operations
55
+ self.send('Local Rows')
56
+
54
57
  def receive(self) -> Result[Response, str]:
55
58
  for _ in range(10):
56
59
  try:
@@ -60,6 +63,9 @@ class TableController(abc.ABC):
60
63
  return Err("Could not parse response")
61
64
 
62
65
  def send(self, cmd: Union[Command, str]):
66
+ if not self.controller:
67
+ raise RuntimeError(
68
+ "Communication controller must be initialized before sending command. It is currently in offline mode.")
63
69
  self.controller.send(cmd)
64
70
 
65
71
  def sleepy_send(self, cmd: Union[Command, str]):
@@ -85,6 +91,46 @@ class TableController(abc.ABC):
85
91
  row=row,
86
92
  col_name=col_name.value))
87
93
 
94
+ def add_new_col_num(self,
95
+ col_name: RegisterFlag,
96
+ val: Union[int, float]):
97
+ self.sleepy_send(TableOperation.NEW_COL_VAL.value.format(
98
+ register=self.table.register,
99
+ table_name=self.table.name,
100
+ col_name=col_name,
101
+ val=val))
102
+
103
+ def add_new_col_text(self,
104
+ col_name: RegisterFlag,
105
+ val: str):
106
+ self.sleepy_send(TableOperation.NEW_COL_TEXT.value.format(
107
+ register=self.table.register,
108
+ table_name=self.table.name,
109
+ col_name=col_name,
110
+ val=val))
111
+
112
+ def edit_row_num(self,
113
+ col_name: RegisterFlag,
114
+ val: Union[int, float],
115
+ row: Optional[int] = None):
116
+ self.sleepy_send(TableOperation.EDIT_ROW_VAL.value.format(
117
+ register=self.table.register,
118
+ table_name=self.table.name,
119
+ row=row if row is not None else 'Rows',
120
+ col_name=col_name,
121
+ val=val))
122
+
123
+ def edit_row_text(self,
124
+ col_name: RegisterFlag,
125
+ val: str,
126
+ row: Optional[int] = None):
127
+ self.sleepy_send(TableOperation.EDIT_ROW_TEXT.value.format(
128
+ register=self.table.register,
129
+ table_name=self.table.name,
130
+ row=row if row is not None else 'Rows',
131
+ col_name=col_name,
132
+ val=val))
133
+
88
134
  @abc.abstractmethod
89
135
  def get_row(self, row: int):
90
136
  pass
@@ -150,7 +196,7 @@ class TableController(abc.ABC):
150
196
  return started_running
151
197
 
152
198
  def check_hplc_done_running(self,
153
- method: Optional[MethodTimetable] = None,
199
+ method: Optional[MethodDetails] = None,
154
200
  sequence: Optional[SequenceTable] = None) -> Result[str, str]:
155
201
  """
156
202
  Checks if ChemStation has finished running and can read data back
@@ -213,4 +259,8 @@ class TableController(abc.ABC):
213
259
  Load chromatogram for any channel in spectra dictionary.
214
260
  """
215
261
  for channel, spec in self.spectra.items():
216
- spec.load_spectrum(data_path=data_file, channel=channel)
262
+ try:
263
+ spec.load_spectrum(data_path=data_file, channel=channel)
264
+ except FileNotFoundError:
265
+ self.spectra[channel] = None
266
+ print(f"No data at channel: {channel}")