BioImager  3.9.1
A .NET microscopy imaging library. Supports various microscopes by using imported libraries & GUI automation. Supported libraries include PriorĀ® & ZeissĀ® & all devices supported by Micromanager 2.0 and python-microscope.
Loading...
Searching...
No Matches
linkam.py
1#!/usr/bin/env python3
2
3## Copyright (C) 2020 Mick Phillips <mick.phillips@gmail.com>
4##
5## This file is part of Microscope.
6##
7## Microscope is free software: you can redistribute it and/or modify
8## it under the terms of the GNU General Public License as published by
9## the Free Software Foundation, either version 3 of the License, or
10## (at your option) any later version.
11##
12## Microscope is distributed in the hope that it will be useful,
13## but WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15## GNU General Public License for more details.
16##
17## You should have received a copy of the GNU General Public License
18## along with Microscope. If not, see <http://www.gnu.org/licenses/>.
19
20"""A microscope interface to Linkam stages.
21
22This module requires the LinkamSDK library and a license file, available
23from Linkam Scientific Instruments.
24
25Currently, this module supports on the the correlative microscopy stage,
26but should be readily extensible to support other Linkam stages.
27
28.. note::
29
30 This module does not run correctly with python optimisations in
31 use. When invoked with `python -O`, there seem to be issues with
32 accessing ctypes objects.
33
34* `get_status()` throws `AttributeError` "c_ulonglong has no attribute 'flags'";
35* `get_id` returns an empty string, not the device serial number.
36"""
37
38import ctypes
39import datetime
40import os
41import os.path
42import threading
43import time
44from ctypes import POINTER, byref
45from enum import Enum, IntEnum
46
47import microscope
48import microscope.abc
49
50
51_max_version_length = 20
52
53# Typedefs from C headers
54_int8_t = ctypes.c_int8
55_uint8_t = ctypes.c_uint8
56_int16_t = ctypes.c_int16
57_uint16_t = ctypes.c_uint16
58_int32_t = ctypes.c_int32
59_uint32_t = ctypes.c_uint32
60_int64_t = ctypes.c_int64
61_uint64_t = ctypes.c_uint64
62_float64_t = ctypes.c_double
63_float32_t = ctypes.c_float
64_float64_t = ctypes.c_double
65_float32_t = ctypes.c_float
66_CommsHandle = ctypes.c_uint64
67
68
69class _CommsInfo(ctypes.Structure):
70 """CommsInfo struct from C headers"""
71
72 _fields_ = [("type", ctypes.c_uint), ("info", ctypes.c_char * 124)]
73
74 @property
75 def view_of_info(self):
76 """Provide a view of the info field so that its subfields can be accessed"""
77 if self.type == 0:
78 ptype = None
79 elif self.type == 1:
80 ptype = POINTER(_SerialCommsInfo)
81 elif self.type == 2:
82 ptype = POINTER(_USBCommsInfo)
83
84 if ptype is None:
85 return self.info
86 else:
87 offset = getattr(_CommsInfo, "info").offset
88 return _USBCommsInfo.from_buffer(self, offset)
89
90
91class _SerialCommsInfo(ctypes.Structure):
92 """SerialCommsInfo struct from C headers"""
93
94 _fields_ = [
95 ("port", ctypes.c_char * 64),
96 ("baudrate", ctypes.c_uint32),
97 ("bytesize", ctypes.c_uint),
98 ("parity", ctypes.c_uint),
99 ("stopbits", ctypes.c_uint),
100 ("flowcontrol", ctypes.c_uint),
101 ("timeout", ctypes.c_uint32),
102 ("padding", ctypes.c_uint8 * 36),
103 ]
104
105
106class _USBCommsInfo(ctypes.Structure):
107 """USBCommsInfo struct from C headers"""
108
109 _fields_ = [
110 ("vendorID", ctypes.c_uint16),
111 ("productID", ctypes.c_uint16),
112 ("serialNumber", ctypes.c_char * 17),
113 ("padding", ctypes.c_uint8 * 83),
114 ]
115
116
117class _StageGroup(Enum):
118 """StageGroup enum from C headers"""
119
120 START = 0x0000
121 Standard = 0x0000
122 Peltier = 0x0001
123 Gradient = 0x0002
124 DifferentialScanningCalorimetry = 0x0003
125 Vacuum = 0x0004
126 Pressure = 0x0005
127 MotorDriven = 0x0006
128 TensileTest = 0x0007
129 CambridgeShearingSystem = 0x0008
130 TemperatureControlled = 0x0009
131 Warm = 0x000A
132 CorrelativeMicroscopy = 0x000B
133 IndiumTinOxideWarm = 0x000C
134 TemperatureControlledVacuum = 0x000D
135 TensileTestV2 = 0x000E
136 DifferentialScanningCalorimetryV2 = 0x000F
137 FreezeDryingVialSystem = 0x0010
138 MAX = 0x7FF
139
140
141class _StageConfigFlags(ctypes.Structure):
142 """StageConfig.flags struct from C headers"""
143
144 _fields_ = [
145 ("standardStage", ctypes.c_uint, 1),
146 ("highTempStage", ctypes.c_uint, 1),
147 ("peltierStage", ctypes.c_uint, 1),
148 ("gradedStage", ctypes.c_uint, 1),
149 ("tensileStage", ctypes.c_uint, 1),
150 ("dscStage", ctypes.c_uint, 1),
151 ("warmStage", ctypes.c_uint, 1),
152 ("itoStage", ctypes.c_uint, 1),
153 ("css450Stage", ctypes.c_uint, 1),
154 ("correlativeStage", ctypes.c_uint, 1),
155 ("unused10", ctypes.c_uint, 1),
156 ("unused11", ctypes.c_uint, 1),
157 ("unused12", ctypes.c_uint, 1),
158 ("unused13", ctypes.c_uint, 1),
159 ("unused14", ctypes.c_uint, 1),
160 ("unused15", ctypes.c_uint, 1),
161 ("unused16", ctypes.c_uint, 1),
162 ("unused17", ctypes.c_uint, 1),
163 ("unused18", ctypes.c_uint, 1),
164 ("unused19", ctypes.c_uint, 1),
165 ("unused20", ctypes.c_uint, 1),
166 ("coolingManual", ctypes.c_uint, 1),
167 ("coolingAutomatic", ctypes.c_uint, 1),
168 ("coolingDual", ctypes.c_uint, 1),
169 ("coolingDualSpeedIndependent", ctypes.c_uint, 1),
170 ("unused25", ctypes.c_uint, 1),
171 ("heater1", ctypes.c_uint, 1),
172 ("heater1TempCtrl", ctypes.c_uint, 1),
173 ("heater1TempCtrlProbe", ctypes.c_uint, 1),
174 ("unused29", ctypes.c_uint, 1),
175 ("unused30", ctypes.c_uint, 1),
176 ("unused31", ctypes.c_uint, 1),
177 ("unused32", ctypes.c_uint, 1),
178 ("unused33", ctypes.c_uint, 1),
179 ("unused34", ctypes.c_uint, 1),
180 ("unused35", ctypes.c_uint, 1),
181 ("heater2", ctypes.c_uint, 1),
182 ("heater12IndependentLimits", ctypes.c_uint, 1),
183 ("unused38", ctypes.c_uint, 1),
184 ("unused39", ctypes.c_uint, 1),
185 ("unused40", ctypes.c_uint, 1),
186 ("unused41", ctypes.c_uint, 1),
187 ("unused42", ctypes.c_uint, 1),
188 ("unused43", ctypes.c_uint, 1),
189 ("unused44", ctypes.c_uint, 1),
190 ("unused45", ctypes.c_uint, 1),
191 ("waterCoolingSensorFitted", ctypes.c_uint, 1),
192 ("home", ctypes.c_uint, 1),
193 ("supportsVacuum", ctypes.c_uint, 1),
194 ("motorX", ctypes.c_uint, 1),
195 ("motorY", ctypes.c_uint, 1),
196 ("motorZ", ctypes.c_uint, 1),
197 ("supportsHumidity", ctypes.c_uint, 1),
198 ("unused53", ctypes.c_uint, 1),
199 ("unused54", ctypes.c_uint, 1),
200 ("unused55", ctypes.c_uint, 1),
201 ("unused56", ctypes.c_uint, 1),
202 ("unused57", ctypes.c_uint, 1),
203 ("unused58", ctypes.c_uint, 1),
204 ("unused59", ctypes.c_uint, 1),
205 ("unused60", ctypes.c_uint, 1),
206 ("unused61", ctypes.c_uint, 1),
207 ("unused62", ctypes.c_uint, 1),
208 ("unused63", ctypes.c_uint, 1),
209 ]
210
211
212class _StageConfig(ctypes.Union):
213 """StageConfig union from C headers."""
214
215 _fields_ = [("flags", _StageConfigFlags), ("value", _uint64_t)]
216
217
218class _CMSStatusFlags(ctypes.Structure):
219 """CMSStatus.flags struct from C headers"""
220
221 _fields_ = [
222 ("on", ctypes.c_uint, 1),
223 ("onNoLN2", ctypes.c_uint, 1),
224 ("prime", ctypes.c_uint, 1),
225 ("autoTopUp", ctypes.c_uint, 1),
226 ("warmingUp", ctypes.c_uint, 1),
227 ("WarmingUpFromCupboard", ctypes.c_uint, 1),
228 ("unused6", ctypes.c_uint, 1),
229 ("unused7", ctypes.c_uint, 1),
230 ("light", ctypes.c_uint, 1),
231 ("sampleDewarFillSignal", ctypes.c_uint, 1),
232 ("mainDewarFillSignal", ctypes.c_uint, 1),
233 ("unused11", ctypes.c_uint, 1),
234 ("unused12", ctypes.c_uint, 1),
235 ("unused13", ctypes.c_uint, 1),
236 ("unused14", ctypes.c_uint, 1),
237 ("unused15", ctypes.c_uint, 1),
238 ("unused16", ctypes.c_uint, 1),
239 ("unused17", ctypes.c_uint, 1),
240 ("unused18", ctypes.c_uint, 1),
241 ("unused19", ctypes.c_uint, 1),
242 ("unused20", ctypes.c_uint, 1),
243 ("unused21", ctypes.c_uint, 1),
244 ("unused22", ctypes.c_uint, 1),
245 ("unused23", ctypes.c_uint, 1),
246 ("unused24", ctypes.c_uint, 1),
247 ("unused25", ctypes.c_uint, 1),
248 ("unused26", ctypes.c_uint, 1),
249 ("unused27", ctypes.c_uint, 1),
250 ("unused28", ctypes.c_uint, 1),
251 ("unused29", ctypes.c_uint, 1),
252 ("unused30", ctypes.c_uint, 1),
253 ("unused31", ctypes.c_uint, 1),
254 ]
255
256
257class _CMSStatus(ctypes.Union):
258 """CMSStatus union from C headers"""
259
260 _fields_ = [("flags", _CMSStatusFlags), ("value", _uint32_t)]
261
262
263class _CMSErrorFlags(ctypes.Structure):
264 """CMSError.flags struct from C headers"""
265
266 _fields_ = [
267 ("mainSensorOC", ctypes.c_uint, 1),
268 ("mainSensorOver", ctypes.c_uint, 1),
269 ("ln2SwitchSensorOC", ctypes.c_uint, 1),
270 ("ln2SwitchSensorOver", ctypes.c_uint, 1),
271 ("dewarSensorOC", ctypes.c_uint, 1),
272 ("dewarSensorOver", ctypes.c_uint, 1),
273 ("baseSensorOC", ctypes.c_uint, 1),
274 ("baseSensorOver", ctypes.c_uint, 1),
275 ("dewarEmpty", ctypes.c_uint, 1),
276 ("motorPosnError", ctypes.c_uint, 1),
277 ("unused10", ctypes.c_uint, 1),
278 ("unused11", ctypes.c_uint, 1),
279 ("unused12", ctypes.c_uint, 1),
280 ("unused13", ctypes.c_uint, 1),
281 ("unused14", ctypes.c_uint, 1),
282 ("unused15", ctypes.c_uint, 1),
283 ("unused16", ctypes.c_uint, 1),
284 ("unused17", ctypes.c_uint, 1),
285 ("unused18", ctypes.c_uint, 1),
286 ("unused19", ctypes.c_uint, 1),
287 ("unused20", ctypes.c_uint, 1),
288 ("unused21", ctypes.c_uint, 1),
289 ("unused22", ctypes.c_uint, 1),
290 ("unused23", ctypes.c_uint, 1),
291 ("unused24", ctypes.c_uint, 1),
292 ("unused25", ctypes.c_uint, 1),
293 ("unused26", ctypes.c_uint, 1),
294 ("unused27", ctypes.c_uint, 1),
295 ("unused28", ctypes.c_uint, 1),
296 ("unused29", ctypes.c_uint, 1),
297 ("unused30", ctypes.c_uint, 1),
298 ("unused31", ctypes.c_uint, 1),
299 ]
300
301
302class _CMSError(ctypes.Union):
303 """CMSError union from C headers"""
304
305 _fields_ = [("flags", _CMSErrorFlags), ("value", _uint32_t)]
306
307
308class _ConnectionStatusFlags(ctypes.Structure):
309 """ConnectionStatus.flags structure from C headers"""
310
311 _fields_ = [
312 ("connected", ctypes.c_uint, 1),
313 ("errorNoDeviceFound", ctypes.c_uint, 1),
314 ("errorMultipleDevicesFound", ctypes.c_uint, 1),
315 ("errorTimeout", ctypes.c_uint, 1),
316 ("errorHandleRegistrationFailed", ctypes.c_uint, 1),
317 ("errorAllocationFailed", ctypes.c_uint, 1),
318 ("errorSerialNumberRequired", ctypes.c_uint, 1),
319 ("errorAlreadyOpen", ctypes.c_uint, 1),
320 ("errorPropertiesIncorrect", ctypes.c_uint, 1),
321 ("errorPortConfig", ctypes.c_uint, 1),
322 ("errorCommsStreams", ctypes.c_uint, 1),
323 ("errorUnhandled", ctypes.c_uint, 1),
324 ("unused12", ctypes.c_uint, 1),
325 ("unused13", ctypes.c_uint, 1),
326 ("unused14", ctypes.c_uint, 1),
327 ("unused15", ctypes.c_uint, 1),
328 ("unused16", ctypes.c_uint, 1),
329 ("unused17", ctypes.c_uint, 1),
330 ("unused18", ctypes.c_uint, 1),
331 ("unused19", ctypes.c_uint, 1),
332 ("unused20", ctypes.c_uint, 1),
333 ("unused21", ctypes.c_uint, 1),
334 ("unused22", ctypes.c_uint, 1),
335 ("unused23", ctypes.c_uint, 1),
336 ("unused24", ctypes.c_uint, 1),
337 ("unused25", ctypes.c_uint, 1),
338 ("unused26", ctypes.c_uint, 1),
339 ("unused27", ctypes.c_uint, 1),
340 ("unused28", ctypes.c_uint, 1),
341 ("unused29", ctypes.c_uint, 1),
342 ("unused30", ctypes.c_uint, 1),
343 ("unused31", ctypes.c_uint, 1),
344 ]
345
346
347class _ConnectionStatus(ctypes.Union):
348 """ConnectionStatus union from C headers"""
349
350 _fields_ = [("flags", _ConnectionStatusFlags), ("value", _uint32_t)]
351
352
353class _ControllerStatusFlags(ctypes.Structure):
354 """ControllerStatus.flags struct from C headers"""
355
356 _fields_ = [
357 ("controllerError", ctypes.c_uint, 1),
358 ("heater1RampSetPoint", ctypes.c_uint, 1),
359 ("heater1Started", ctypes.c_uint, 1),
360 ("heater2RampSetPoint", ctypes.c_uint, 1),
361 ("heater2Started", ctypes.c_uint, 1),
362 ("vacuumRampSetPoint", ctypes.c_uint, 1),
363 ("vacuumCtrlStarted", ctypes.c_uint, 1),
364 ("vacuumValveClosed", ctypes.c_uint, 1),
365 ("vacuumValveOpen", ctypes.c_uint, 1),
366 ("humidityRampSetPoint", ctypes.c_uint, 1),
367 ("humidityCtrlStarted", ctypes.c_uint, 1),
368 ("lnpCoolingPumpOn", ctypes.c_uint, 1),
369 ("lnpCoolingPumpAuto", ctypes.c_uint, 1),
370 ("unused13", ctypes.c_uint, 1),
371 ("HumidityDesiccantConditioning", ctypes.c_uint, 1),
372 ("unused15", ctypes.c_uint, 1),
373 ("unused16", ctypes.c_uint, 1),
374 ("unused17", ctypes.c_uint, 1),
375 ("unused18", ctypes.c_uint, 1),
376 ("unused19", ctypes.c_uint, 1),
377 ("unused20", ctypes.c_uint, 1),
378 ("unused21", ctypes.c_uint, 1),
379 ("unused22", ctypes.c_uint, 1),
380 ("unused23", ctypes.c_uint, 1),
381 ("unused24", ctypes.c_uint, 1),
382 ("unused25", ctypes.c_uint, 1),
383 ("unused26", ctypes.c_uint, 1),
384 ("unused27", ctypes.c_uint, 1),
385 ("unused28", ctypes.c_uint, 1),
386 ("unused29", ctypes.c_uint, 1),
387 ("unused30", ctypes.c_uint, 1),
388 ("unused31", ctypes.c_uint, 1),
389 ("unused32", ctypes.c_uint, 1),
390 ("unused33", ctypes.c_uint, 1),
391 ("unused34", ctypes.c_uint, 1),
392 ("unused35", ctypes.c_uint, 1),
393 ("unused36", ctypes.c_uint, 1),
394 ("unused37", ctypes.c_uint, 1),
395 ("unused38", ctypes.c_uint, 1),
396 ("unused39", ctypes.c_uint, 1),
397 ("unused40", ctypes.c_uint, 1),
398 ("motorTravelMinX", ctypes.c_uint, 1),
399 ("motorTravelMaxX", ctypes.c_uint, 1),
400 ("motorStoppedX", ctypes.c_uint, 1),
401 ("motorTravelMinY", ctypes.c_uint, 1),
402 ("motorTravelMaxY", ctypes.c_uint, 1),
403 ("motorStoppedY", ctypes.c_uint, 1),
404 ("motorTravelMinZ", ctypes.c_uint, 1),
405 ("motorTravelMaxZ", ctypes.c_uint, 1),
406 ("motorStoppedZ", ctypes.c_uint, 1),
407 ("sampleCal", ctypes.c_uint, 1),
408 ("motorDistanceCalTST", ctypes.c_uint, 1),
409 ("cssRotMotorStopped", ctypes.c_uint, 1),
410 ("cssGapMotorStopped", ctypes.c_uint, 1),
411 ("cssLidOn", ctypes.c_uint, 1),
412 ("cssRefLimit", ctypes.c_uint, 1),
413 ("cssZeroLimit", ctypes.c_uint, 1),
414 ("unused57", ctypes.c_uint, 1),
415 ("unused58", ctypes.c_uint, 1),
416 ("unused59", ctypes.c_uint, 1),
417 ("unused60", ctypes.c_uint, 1),
418 ("unused61", ctypes.c_uint, 1),
419 ("unused62", ctypes.c_uint, 1),
420 ("unused63", ctypes.c_uint, 1),
421 ]
422
423
424class _ControllerStatus(ctypes.Union):
425 """ControllerStatus union from C headers"""
426
427 _fields_ = [("flags", _ControllerStatusFlags), ("value", _uint64_t)]
428
429
430class _MDSStatusFlags(ctypes.Structure):
431 """MDSStatus.flags struct from C headers"""
432
433 _fields_ = [
434 ("xMinLimit", ctypes.c_uint, 1),
435 ("xMaxLimit", ctypes.c_uint, 1),
436 ("xMoveDone", ctypes.c_uint, 1),
437 ("yMinLimit", ctypes.c_uint, 1),
438 ("yMaxLimit", ctypes.c_uint, 1),
439 ("yMoveDone", ctypes.c_uint, 1),
440 ("unused6", ctypes.c_uint, 1),
441 ("unused7", ctypes.c_uint, 1),
442 ("unused8", ctypes.c_uint, 1),
443 ("unused9", ctypes.c_uint, 1),
444 ("unused10", ctypes.c_uint, 1),
445 ("unused11", ctypes.c_uint, 1),
446 ("unused12", ctypes.c_uint, 1),
447 ("unused13", ctypes.c_uint, 1),
448 ("unused14", ctypes.c_uint, 1),
449 ("unused15", ctypes.c_uint, 1),
450 ("unused16", ctypes.c_uint, 1),
451 ("unused17", ctypes.c_uint, 1),
452 ("unused18", ctypes.c_uint, 1),
453 ("unused19", ctypes.c_uint, 1),
454 ("unused20", ctypes.c_uint, 1),
455 ("unused21", ctypes.c_uint, 1),
456 ("unused22", ctypes.c_uint, 1),
457 ("unused23", ctypes.c_uint, 1),
458 ("unused24", ctypes.c_uint, 1),
459 ("unused25", ctypes.c_uint, 1),
460 ("unused26", ctypes.c_uint, 1),
461 ("unused27", ctypes.c_uint, 1),
462 ("unused28", ctypes.c_uint, 1),
463 ("unused29", ctypes.c_uint, 1),
464 ("unused30", ctypes.c_uint, 1),
465 ("unused31", ctypes.c_uint, 1),
466 ]
467
468
469class _MDSStatus(ctypes.Union):
470 """MDSStatus union from C headers"""
471
472 _fields_ = [("flags", _MDSStatusFlags), ("value", _uint32_t)]
473
474
475class ControllerError(Enum):
476 """ControllerError enum from C headers"""
477
478 NoError = 0
479 StageCableDisconnected = 1
480 StageCableError = 2
481 StageTempSensorOpenOverrange = 3
482 LoadPowerOutputVoltageWrong = 4
483 T95RelayMissing = 5
484 T95OptionBoardWongConfig = 6
485 OptionBoardCableDisconnect = 7
486 LoadPowerIncorrectForStage = 8
487 OptionBoardIncorrectCable = 9
488 OptionBoardSensorOenOverrange = 10
489 T95FanNotWorking = 11
490 LNP95Error = 12
491 CommsError = 13
492 CoolingWaterTooWarmNotFlowing = 14
493 CSS450MotorDriveOverTemp = 15
494 CSS450MotorWindingError1 = 16
495 CSS450MotorWindingError2 = 17
496 Reserved1 = 18
497 Reserved2 = 19
498 Reserved3 = 20
499 CMS196ChamberSensorOpen = 21
500 CMS196ChamberSensorOverrange = 22
501 CMS196LN2SwitchSensorOpen = 23
502 CMS196LN2SwitchSensorOverrange = 24
503 CMS196DewarSensorOpen = 25
504 CMS196DewarSensorOverrange = 26
505 CMS196DewarEmpty = 27
506 CMS196BaseSensorOpen = 28
507 CMS196BaseSensorOverrange = 29
508 CMS196MotorPosnError = 30
509
510
511# LinkamFunctionMsgCode enum from C headers.
512class Msg(IntEnum):
513 OpenComms = 0x00000001
514 # \param[in] hDevice A valid handle to a comms device/port returned by eLinkamFunctionMsgCode_OpenComms.
515 CloseComms = 0x00000002
516 GetControllerConfig = 0x00000003
517 GetControllerError = 0x00000004
518 GetControllerName = 0x00000005
519 GetControllerSerial = 0x00000006
520 GetStatus = 0x00000007
521 GetStageConfig = 0x00000008
522 GetStageSerial = 0x00000009
523 GetStageName = 0x0000000A
524 GetMaxValue = 0x0000000B
525 GetMinValue = 0x0000000C
526 GetResolution = 0x0000000D
527 ApplySampleCals = 0x0000000E
528 SaveSampleCals = 0x0000000F
529 StartHeating = 0x00000010
530 StartVacuum = 0x00000011
531 StartHumidity = 0x00000012
532 StartHumidityDesiccantConditioning = 0x00000013
533 StartMotors = 0x00000014
534 GetValue = 0x00000015
535 SetValue = 0x00000016
536 TstCalibrateDistance = 0x00000017
537 TstSetMode = 0x00000018
538 TstZeroForce = 0x00000019
539 TstZeroPosition = 0x0000001A
540 LnpSetMode = 0x0000001B
541 LnpSetSpeed = 0x0000001C
542 CssApplyValues = 0x0000001D
543 CssCheckValues = 0x0000001E
544 CssGotoReference = 0x0000001F
545 CssSensorCal = 0x00000020
546 CssStartJogGap = 0x00000021
547 CssStartJogRot = 0x00000022
548 EnableLogging = 0x00000023
549 DisableLogging = 0x00000024
550 GetControllerFirmwareVersion = 0x00000025
551 GetControllerHardwareVersion = 0x00000026
552 GetStageFirmwareVersion = 0x00000027
553 GetStageHardwareVersion = 0x00000028
554 GetDataRate = 0x00000029
555 SetDataRate = 0x0000002A
556 GetStageCableLimits = 0x0000002B
557 SendDscGainValues = 0x0000002C
558 SendDscPowerValue = 0x0000002D
559 SendDscBaselinePowerValues = 0x0000002E
560 SendDscTuaConstants = 0x0000002F
561 SetDSCModulationData = 0x00000030
562 GetOptionCardType = 0x00000031
563 GetOptionCardSlot = 0x00000032
564 GetOptionCardName = 0x00000033
565 GetOptionCardSerial = 0x00000034
566 GetOptionCardHardwareVersion = 0x00000035
567 DoesOptionCardSupportSensors = 0x00000036
568 # \see eLinkamFunctionMsgCode_DoesOptionCardSupportSensors
569 GetOptionCardSensorName = 0x00000037
570 # \see eLinkamFunctionMsgCode_DoesOptionCardSupportSensors
571 GetOptionCardSensorSerial = 0x00000038
572 # \see eLinkamFunctionMsgCode_DoesOptionCardSupportSensors
573 GetOptionCardSensorHardwareVersion = 0x00000039
574 GetStageGroup = 0x0000003A
575 HaveInstrumentBusDeviceType = 0x0000003B
576 GetInstrumentBusDeviceName = 0x0000003C
577 GetInstrumentBusDeviceSerial = 0x0000003D
578 GetInstrumentBusDeviceFirmwareVersion = 0x0000003E
579 GetInstrumentBusDeviceHardwareVersion = 0x0000003F
580 GetHumidityControllerSensorName = 0x00000040
581 GetHumidityControllerSensorSerial = 0x00000041
582 GetHumidityControllerSensorHardwareVersion = 0x00000042
583 IsControllerType = 0x00000043
584 GetControllerPSUDetails = 0x00000044
585 SetControllerTriggerSignalEnable = 0x00000045
586 SetControllerTriggerSignalDisable = 0x00000046
587 SetControllerMainsFrequency = 0x00000047
588 InitialiseTriggerSignalPulse = 0x00000048
589 SetTriggerSignalPulse = 0x00000049
590 SetTriggerSignalPluseWidth = 0x0000004A
591 GetProgramState = 0x0000004B
592 GetStageConfiguration = 0x0000004C
593 GetControllerHeaterDetails = 0x0000004D
594 CssSendGapVelocity = 0x0000004E
595 CssSendGapOverride = 0x0000004F
596 CssSendGap = 0x00000050
597 CssSendVelocity = 0x00000051
598 CssSendRate = 0x00000052
599 CssSendFrequency = 0x00000053
600 CssSendStrain = 0x00000054
601 CssSendDirection = 0x00000055
602 CssSendForceStop = 0x00000056
603 CssSendTorque = 0x00000057
604 TstSetCalibrationForce = 0x00000058
605 ForceHeating = 0x00000059
606 ForceCooling = 0x0000005A
607 ForceHold = 0x0000005B
608 GetInstrumentBusDeviceIdent = 0x0000005C
609 GetStageIdent = 0x0000005D
610 GetControllerIdent = 0x0000005F
611 GetConnectionInformation = 0x00000060
612 GetStageHeaterIdent = 0x00000061
613 Max = 0x0FFFFFFF
614
615
616class ErrorCode(Enum):
617 """ErrorCode enum from C headers"""
618
619 NoError = 0xECF00000
620 LibraryNotInitialised = 0xECF00001
621 NoConnectionInfo = 0xECF00002
622 DeviceRegistrationFailed = 0xECF00003
623 DeviceCreationFailure = 0xECF00004
624 SerialCommsInitialisationFailure = 0xECF00005
625 SerialCommsHandshakeFailure = 0xECF00006
626 SerialPortSocketCreationFailure = 0xECF00007
627 SerialPortSocketConfigurationFailure = 0xECF00008
628 SerialCommsRxError = 0xECF00009
629 SerialCommsUnknownRxError = 0xECF0000A
630 CommandBufferLimitReached = 0xECF0000B
631 USBCommsInitialisationFailure = 0xECF0000C
632 USBCommsHandshakeFailure = 0xECF0000D
633 USBPortSocketCreationFailure = 0xECF0000E
634 USBCommsRxError = 0xECF0000F
635 USBCommsUnknownRxError = 0xECF00010
636 USBCommsTxError = 0xECF00011
637 USBCommsUnknownTxError = 0xECF00012
638 SerialCommsTxError = 0xECF00013
639 SerialCommsUnknownTxError = 0xECF00014
640 Max = 0xECFFFFFF
641
642
643class _StageValueType(Enum):
644 """StageValueType enum from C headers"""
645
646 Heater1Temp = 0
647 HeaterRate = 1
648 HeaterSetpoint = 2
649 Heater1Power = 3
650 Heater1LNPSpeed = 4
651 Heater2Temp = 5
652 Heater2Power = 8
653 Heater2LNPSpeed = 9
654 WaterCoolingTemp = 10
655 HumidityTemp = 11
656 Vacuum = 12
657 VacuumSetpoint = 13
658 Humidity = 14
659 HumiditySetpoint = 15
660 MotorPosX = 16
661 MotorVelX = 17
662 MotorSetpointX = 18
663 MotorPosY = 19
664 MotorVelY = 20
665 MotorSetpointY = 21
666 MotorPosZ = 22
667 MotorVelZ = 23
668 MotorSetpointZ = 24
669 MotorDrivenStageStatus = 25
670 VacuumBoardUnitOfMeasure = 26
671 VacMotorValveStatus = 27
672 VacMotorValvePos = 28
673 VacMotorValveVel = 29
674 VacMotorValveSetpoint = 30
675 GradedMotorPos = 31
676 GradedMotorVel = 32
677 GradedMotorDistanceSetpoint = 33
678 SampleRef1 = 34
679 SampleAct1 = 35
680 SampleRef2 = 36
681 SampleAct2 = 37
682 SampleRef3 = 38
683 SampleAct3 = 39
684 SampleRef4 = 40
685 SampleAct4 = 41
686 SampleRef5 = 42
687 SampleAct5 = 43
688 Heater3Temp = 44
689 Dsc = 45
690 TriggerSignalBlue = 46
691 TriggerSignalGreen = 47
692 TriggerSignalPink = 48
693 TriggerSignalsEnabled = 49
694 TemperatureResolution = 50
695 Heater4Temp = 51
696 CmsLight = 52
697 CmsWarmingHeater = 53
698 CmsSolenoidRefill = 54
699 CmsSampleDewarFillSig = 55
700 CmsStatus = 56
701 CmsError = 57
702 RampHoldTime = 58
703 RampHoldRemaining = 59
704 CmsMainDewarFillSig = 60
705 CmsCondenserLEDLevel = 61
706 TestMotion = 62
707 MotorFeedbackYX = 63
708 TstMotorPos = 64
709 TstMotorVel = 65
710 TstMotorDistanceSetpoint = 66
711 TstForce = 67
712 TstForceSetpoint = 68
713 TstPidKp = 69
714 TstPidKi = 70
715 TstPidKd = 71
716 TstForceGauge = 72
717 CssMode = 73
718 CssGapSetpoint = 74
719 CssGapPos = 75
720 CssStrainSetpoint = 76
721 CssRateSetpoint = 77
722 CssOcsFreq = 78
723 CssDirn = 79
724 CssJogRotVel = 80
725 CssJogGapDis = 81
726 CssDefaultGapRefVel = 82
727 CssDefaultRotRefVel = 83
728 CssStepDone = 84
729 CssStepSuccess = 85
730 CssStatus = 86
731 CssForce = 87
732 CssShareTime = 88
733 CssRotMotorVelocitySetpoint = 89
734 CssGapMotorVelocitySetpoint = 90
735 CssOptionBoardSensorData = 91
736 RS232OptionBoardSensorEnabled = 92
737 VacuumOptionBoardSensor1Data = 93
738 VacuumOptionBoardSensor1Enabled = 94
739 VacuumOptionBoardSensor2Data = 95
740 VacuumOptionBoardSensor2Enabled = 96
741 VtoOptionBoardEnabled = 97
742 CmsDewarTopTemperature = 98
743 CmsAutoDewarFill = 99
744 DscPower = 100
745 DscGain1 = 101
746 DscGain2 = 102
747 DscGain3 = 103
748 DscConstantTerm = 104
749 DscPowerTerm1 = 105
750 DscPowerTerm2 = 106
751 DscPowerTerm3 = 107
752 DscPowerTerm4 = 108
753 DscPowerTerm5 = 109
754 DscPowerTerm6 = 110
755 DscBaselineConstTerm = 111
756 DscBaselinePowerTerm1 = 112
757 DscBaselinePowerTerm2 = 113
758 DscBaselinePowerTerm3 = 114
759 DscBaselinePowerTerm4 = 115
760 DscTuaConst1 = 116
761 DscTuaConst2 = 117
762 DscOptionBoardSensorEnabled = 118
763 TstJawToJawSize = 119
764 TstTableDirection = 120
765 TstSampleSize = 121
766 TstStrainEngineeringUnits = 122
767 TstStrainPercentage = 123
768 TstShowAsForceDistance = 124
769 TstCalForceValue = 125
770 TstOptionBoardSensorEnabled = 126
771 TstShowCalbData = 127
772 TstStatus = 128
773 TstJawPosition = 129
774 TstStrain = 130
775 TstStress = 131
776 TstTableMode = 132
777 StageHumidityUnitData = 133
778 Pressure = 134
779 MotorXOptionBoardSensorEnabled = 135
780 MotorYOptionBoardSensorEnabled = 136
781 MotorZOptionBoardSensorEnabled = 137
782 MotorVacuumOptionBoardSensorEnabled = 138
783 MotorFDVacuumOptionBoardSensorEnabled = 139
784 MotorTstOptionBoardSensorEnabled = 140
785 MotorGradientOptionBoardSensorEnabled = 141
786 MotorXOptionBoardSensorData = 142
787 MotorYOptionBoardSensorData = 143
788 MotorZOptionBoardSensorData = 144
789 MotorVacuumOptionBoardSensorData = 145
790 MotorFDVacuumOptionBoardSensorData = 146
791 MotorTstOptionBoardSensorData = 147
792 MotorGradientOptionBoardSensorData = 148
793 TtcOptionBoardEnabled = 149
794 TtcOptionBoardSensor1Enabled = 150
795 TtcOptionBoardSensor2Enabled = 151
796 TtcOptionBoardSensor3Enabled = 152
797 DtcOptionBoardSensor1Enabled = 153
798 DtcOptionBoardSensor2Enabled = 154
799 MotorXDefaultSpeed = 155
800 MotorYDefaultSpeed = 156
801 MotorZDefaultSpeed = 157
802 MotorTstDefaultSpeed = 158
803 MotorGsDefaultSpeed = 159
804 MotorVacDefaultSpeed = 160
805 MotorFDVacDefaultSpeed = 161
806 HumidityDryingTimeSetpoint = 162
807 HumiditySwapTimeSetpoint = 163
808 HumidityPipeTempSetpoint = 164
809 HumidityWaterTempSetpoint = 165
810 HumidityDryingTimeLeft = 166
811 HumiditySwapTimeLeft = 167
812 HumidityWaterTemp = 168
813 VtoVideoStandard = 169
814 TriggerSignalPulseWidth = 170
815 ConnectionType = 171
816 VacuumSimulatorPlug = 172
817 PressureSimulatorPlug = 173
818 LNPSingle = 174
819 LNPDual = 175
820 LNP95 = 176
821 LNP96 = 177
822 UsingXenocsStageTestCables = 178
823 UsingXenocsStageTestCableType1 = 179
824 UsingXenocsStageTestCableType2 = 180
825 UsingCalibrationPlug = 181
826 UsingCalibrationCableB = 182
827 UsingCalibrationCableC = 183
828 UsingCalibrationCableA = 184
829 VtoText = 185
830 VtoTime = 186
831 MotorZeroRefX = 187
832 MotorZeroRefY = 188
833 CmsXaxisGridCentre = 189
834 CmsYaxisGridCentre = 190
835 CmsLashWarning = 191
836 CmsBaseHeaterLimit = 192
837 CmsAlarmVolume = 193
838 ManualHumiditySetpoint = 194
839 FDVSColdTrapPumpSpeed = 195
840 FDVSScanMotorPosition = 196
841 ImagingStationBrightness = 197
842 EnableJoyStick = 198
843 DisableJoyStick = 199
844 InvertJoyStickAxisX = 200
845 InvertJoyStickAxisY = 201
846 FDVSMotorVel = 202
847 FDVSMotorDistanceSetpoint = 203
848 CssDefaultGapChangeVel = 204
849 MaxValue = 65535
850
851
852class _Variant(ctypes.Union):
853 """Variant union from C headers"""
854
855 _fields_ = [
856 ("vChar", ctypes.c_char),
857 ("vUint8", _uint8_t),
858 ("vUint16", _uint16_t),
859 ("vUint32", _uint32_t),
860 ("vUint64", _uint64_t),
861 ("vInt8", _int8_t),
862 ("vInt16", _int16_t),
863 ("vInt32", _int32_t),
864 ("vInt64", _int64_t),
865 ("vFloat32", ctypes.c_float),
866 ("vFloat64", ctypes.c_double),
867 ("vPtr", ctypes.c_void_p),
868 ("vBoolean", ctypes.c_bool),
869 # ("vControllerConfig", _ControllerConfig),
870 ("vControllerError", ctypes.c_uint), # _ControllerError enum
871 ("vControllerStatus", _ControllerStatus),
872 ("vConnectionStatus", _ConnectionStatus),
873 # ("vStageValueType", _StageValueType),
874 # ("vStageCableConfig", _StageCableConfig),
875 ("vStageConfig", _StageConfig),
876 ("vStageGroup", ctypes.c_uint), # _StageGroup enum
877 # ("vStageCableLimit", _StageCableLimit),
878 # ("vCSSStatus", _CSSStatus),
879 # ("vCSSCheckCodes", _CSSCheckCodes),
880 # ("vCSSMode", _CSSMode),
881 # ("vCSSState", _CSSState),
882 ("vCMSStatus", _CMSStatus),
883 ("vCMSError", _CMSError),
884 # ("vOptionBoardType", _OptionBoardType),
885 # ("vInstrumentBusDeviceType", _InstrumentBusDeviceType),
886 # ("vControllerType", _ControllerType),
887 # ("vTSTStatus", _TSTStatus),
888 # ("vTSTMode", _TSTMode),
889 # ("vTSTSampleSize", _TSTSampleSize),
890 # ("vMVStatus", _MVStatus),
891 ("vMDSStatus", _MDSStatus),
892 # ("vCommsType", _CommsType),
893 ]
894
895 def __getattribute__(self, name):
896 """Wrap enum variants with their python Enum for convenience"""
897 val = super().__getattribute__(name)
898 if name == "vStageGroup":
899 return _StageGroup(val)
900 elif name == "vControllerError":
901 return ControllerError(val)
902 else:
903 return val
904
905
906# Most GetValue calls return a Variant holding a float. A few pass back
907# other types in the variant, so we map the StageValueType to the appropriate
908# Variant member name.
909_StageValueTypeToVariant = {
910 _StageValueType.MotorDrivenStageStatus: "vMDSStatus", # MDSStatus
911 _StageValueType.VacMotorValveStatus: "vUint32", # MVStatus - flags not yet supported
912 _StageValueType.TriggerSignalsEnabled: "vBoolean",
913 _StageValueType.CmsLight: "vBoolean",
914 _StageValueType.CmsSampleDewarFillSig: "vBoolean",
915 _StageValueType.CmsStatus: "vCMSStatus",
916 _StageValueType.CmsError: "vCMSError",
917 _StageValueType.CmsMainDewarFillSig: "vBoolean",
918 _StageValueType.TestMotion: "vUint16",
919 _StageValueType.MotorFeedbackYX: "vUint16",
920 _StageValueType.CssMode: "vUint32", # CSSMode - enum not yet supported
921 _StageValueType.CssDirn: "vBoolean",
922 _StageValueType.CssStepDone: "vBoolean",
923 _StageValueType.CssStepSuccess: "vBoolean",
924 _StageValueType.CssStatus: "vUint32", # CSSStatus - flags not yet supported
925 _StageValueType.RS232OptionBoardSensorEnabled: "vBoolean",
926 _StageValueType.VacuumOptionBoardSensor1Enabled: "vBoolean",
927 _StageValueType.VacuumOptionBoardSensor2Enabled: "vBoolean",
928 _StageValueType.VtoOptionBoardEnabled: "vBoolean",
929 _StageValueType.DscOptionBoardSensorEnabled: "vBoolean",
930 _StageValueType.TstTableDirection: "vBoolean",
931 # _StageValueType.TstSampleSize: None, # TSTSampleSize - struct not yet supported
932 _StageValueType.TstStrainEngineeringUnits: "vBoolean",
933 _StageValueType.TstShowAsForceDistance: "vBoolean",
934 _StageValueType.TstOptionBoardSensorEnabled: "vBoolean",
935 _StageValueType.TstStatus: "vUint32", # TSTStatus - flags not yet supported
936 _StageValueType.TstTableMode: "vBoolean",
937 _StageValueType.MotorXOptionBoardSensorEnabled: "vBoolean",
938 _StageValueType.MotorYOptionBoardSensorEnabled: "vBoolean",
939 _StageValueType.MotorVacuumOptionBoardSensorEnabled: "vBoolean",
940 _StageValueType.MotorFDVacuumOptionBoardSensorEnabled: "vBoolean",
941 _StageValueType.MotorFDVacuumOptionBoardSensorEnabled: "vBoolean",
942 _StageValueType.MotorGradientOptionBoardSensorEnabled: "vBoolean",
943}
944
945
947 """Base class for connecting to Linkam SDK devices.
948
949 This class deals with SDK initialisation and setting callbacks to
950 handle SDK events. It maintains a map of SDK handle to device instance
951 so that SDK events result in updates to the correct instance.
952 """
953
954 # The ctypes library object. Value is None until SDK initialised.
955 # This is encapsulated within a class so that this module will not break
956 # tests on import in absence of the required library.
957 _lib = None
958 # _SDKInitialised = False
959 # Map SDK handles to device instances
960 _connectionMap = {}
961 # We need to keep references to CFUNCTYPE callbacks.
962 _callbacks = {}
963
964 @staticmethod
966 """Fetch the SDK version."""
967 b = ctypes.create_string_buffer(_max_version_length)
968 __class__._lib.linkamGetVersion(b, _max_version_length)
969 return b.value
970
971 @staticmethod
972 def init_sdk():
973 """Initialise the SDK and set up event callbacks"""
974 try:
975 __class__._lib = ctypes.WinDLL("LinkamSDK.dll")
976 except:
977 # Not tested
978 __class__._lib = ctypes.CDLL("libLinkamSDK.so")
979 _lib = __class__._lib
980 """Initialise the SDK, and create and set the callbacks."""
981 # Omit conditional pending a fix for ctypes issues when optimisations in use.
982 # if __debug__:
983 # sdk_log = b''
984 # else:
985
986 sdk_log = os.devnull
987 lpaths = [
988 os.path.dirname(microscope.abc.__file__),
989 os.path.dirname(__file__),
990 "",
991 ]
992 for p in lpaths:
993 lskpath = os.path.join(p, "Linkam.lsk")
994 if _lib.linkamInitialiseSDK(sdk_log, lskpath.encode(), True) == 1:
995 break
996 else:
998 "No linkam license file (Linkam.lsk) found in %s." % lpaths
999 )
1000
1001 # NewValue event callback
1002 cfunc = ctypes.CFUNCTYPE(_uint32_t, _CommsHandle, _ControllerStatus)(
1003 __class__._on_new_value
1004 )
1005 _lib.linkamSetCallbackNewValue(cfunc)
1006 __class__._callbacks[__class__._on_new_value] = cfunc
1007 # Connection event callback
1008 cfunc = ctypes.CFUNCTYPE(None, _CommsHandle)(__class__._on_connect)
1009 _lib.linkamSetCallbackControllerConnected(cfunc)
1010 __class__._callbacks[__class__._on_connect] = cfunc
1011 # Disconnection event callback
1012 cfunc = ctypes.CFUNCTYPE(None, _CommsHandle)(__class__._on_disconnect)
1013 _lib.linkamSetCallbackControllerDisconnected(cfunc)
1014 __class__._callbacks[__class__._on_disconnect] = cfunc
1015 # Error event callback
1016 cfunc = ctypes.CFUNCTYPE(None, _CommsHandle, _uint32_t)(
1017 __class__._on_error
1018 )
1019 _lib.linkamSetCallbackError(cfunc)
1020 __class__._callbacks[__class__._on_error] = cfunc
1021
1022 @classmethod
1023 def _on_new_value(cls, h: _CommsHandle, status: _ControllerStatus):
1024 """NewValue callback"""
1025 stage = cls._connectionMap.get(h, None)
1026 if not stage:
1027 return 0
1028 stage._update_status(status)
1029 return 1
1030
1031 @classmethod
1032 def _on_error(cls, h: _CommsHandle, errcode: _uint32_t):
1033 """Error event callback"""
1034 err = ErrorCode(errcode)
1035 stage = cls._connectionMap.get(h, None)
1036 if not stage:
1037 return
1038 if err in (
1039 ErrorCode.USBCommsTxError,
1040 ErrorCode.USBCommsRxError,
1041 ErrorCode.SerialCommsTxError,
1042 ErrorCode.SerialCommsRxError,
1043 ):
1044 # Try to re-establish comms.
1045 stage._reopen_comms()
1046 return
1047
1048 @classmethod
1049 def _on_connect(cls, h: _CommsHandle):
1050 """Connection event callback
1051
1052 Connection event only seems to be generated by processing an
1053 OpenComms message - USB connection is not autodetected."""
1054 stage = cls._connectionMap.get(h, None)
1055 if not stage:
1056 return
1057 stage._post_connect()
1058 return
1059
1060 @classmethod
1061 def _on_disconnect(cls, h: _CommsHandle):
1062 """Disconnection event callback
1063
1064 Discconneciton event only seems to be generated by processing a
1065 CloseComms message."""
1066 stage = cls._connectionMap.get(h, None)
1067 if not stage:
1068 return 0
1069 stage._connectionstatus.flags.connected = 0
1070 return
1071
1072 def __init__(self, **kwargs):
1073 """Initalise the device and, if necessary, the SDK."""
1074 # Connection handle, info struct and status struct.
1075 super().__init__(**kwargs)
1076 self._commsinfo = _CommsInfo()
1077 self._h = _CommsHandle()
1079 self._stageconfig = _StageConfig()
1080 # Stage status struct, updated by the NewValue callback.
1081 self._status = _ControllerStatus()
1082 if __class__._lib is None:
1083 try:
1084 self.init_sdk()
1085 except Exception as e:
1086 raise microscope.LibraryLoadError(e) from e
1087 self._reconnect_thread = None
1088
1089 def _do_shutdown(self) -> None:
1090 pass
1091
1092 def __del__(self):
1093 """Close comms on object deletion"""
1094 self._process_msg(Msg.CloseComms)
1095
1096 def _post_connect(self):
1097 """Mixins should implement this method to do post-connection config."""
1098 pass
1099
1100 def _process_msg(
1101 self, msg, param1=None, param2=None, param3=None, result=None
1102 ):
1103 """As the SDK to process a message."""
1104 if result is None:
1105 result = _Variant()
1106 if not self._lib.linkamProcessMessage(
1107 ctypes.c_uint(msg), self._h, byref(result), param1, param2, param3
1108 ):
1109 raise microscope.DeviceError("ProcessMessage error.")
1110 return result
1111
1113 """Raise an exception if the connection is down."""
1114 if not self._connectionstatus.flags.connected:
1115 raise microscope.DeviceError("Stage not connected.")
1116
1117 def get_data_rate(self):
1118 """Return the status update period in seconds."""
1119 return self._process_msg(Msg.GetDataRate).vUint32 / 1000
1120
1121 def get_error(self):
1122 """Fetch the controller error."""
1123 return self._process_msg(Msg.GetControllerError).vControllerError
1124
1125 def get_id(self):
1126 """Fetch the device's serial number"""
1127 buf = ctypes.create_string_buffer(18)
1128 self._process_msg(Msg.GetControllerSerial, byref(buf), 18)
1129 # Decode from bytes to string and strip trailing spaces.
1130 return buf.value.decode().rstrip()
1131
1132 def get_value(self, svt, result=None):
1133 """Fetch a value from the device.
1134
1135 Args:
1136 svt: a StageValueType
1137 result: an existing Variant to use to return a result, or None.
1138 """
1139 # Don't self.check_connection on read as it can cause get_status to throw exception.
1140 # Ensure svt is an Enum member (not a raw value), as it is used as a key.
1141 if isinstance(svt, str):
1142 # Allow access using parameter names - useful for Pyro.
1143 svt = getattr(_StageValueType, svt)
1144 else:
1145 svt = _StageValueType(svt)
1146 # Determine the appropriate Variant member for the value type.
1147 vtype = _StageValueTypeToVariant.get(svt, "vFloat32")
1148 variant = self._process_msg(Msg.GetValue, svt.value, result=result)
1149 if result is not None:
1150 return result
1151 else:
1152 return getattr(variant, vtype)
1153
1154 def get_value_limits(self, svt):
1155 """Returns the bounds for a StageValueType"""
1156 if isinstance(svt, str):
1157 # Allow access using parameter names - useful for Pyro.
1158 svt = getattr(_StageValueType, svt)
1159 else:
1160 svt = _StageValueType(svt)
1161 vtype = _StageValueTypeToVariant.get(svt, "vFloat32")
1162 vmin = self._process_msg(Msg.GetMinValue, svt.value)
1163 vmax = self._process_msg(Msg.GetMaxValue, svt.value)
1164 return tuple(getattr(v, vtype) for v in (vmin, vmax))
1165
1166 def set_value(self, svt, val):
1167 """Set value identified by svt to val"""
1168 self.check_connection()
1169 if isinstance(svt, str):
1170 # Allow access using parameter names - useful for Pyro.
1171 svt = getattr(_StageValueType, svt)
1172 else:
1173 svt = _StageValueType(svt)
1174 vtype = _StageValueTypeToVariant.get(svt, "vFloat32")
1175 return self._process_msg(
1176 Msg.SetValue, _StageValueType(svt).value, _Variant(**{vtype: val})
1177 ).vBoolean
1178
1179 def is_moving(self, axis=None):
1180 """Returns True if the stage is moving, False if stopped
1181
1182 This method isn't on the LinkamMDSMixin because the StageStatus motor
1183 stopped flags appear to be more reliable than the MDSStatus MoveDone
1184 flags."""
1185 if axis is not None and axis.upper() in "XYZ":
1186 has_motor = getattr(
1187 self._stageconfig.flags, "motor" + axis.upper()
1188 )
1189 stopped = getattr(
1190 self._status.flags, "motorStopped" + axis.upper()
1191 )
1192 return has_motor and not stopped
1193 else:
1194 return any(self.is_moving(ax) for ax in "XYZ")
1195
1196 def close_comms(self):
1197 """Close the comms link"""
1198 self._process_msg(Msg.CloseComms, result=self._connectionstatus)
1199
1200 def open_comms(self):
1201 """Open the comms link and store the comms handle."""
1202 self._process_msg(
1203 Msg.OpenComms,
1204 byref(self._commsinfo),
1205 byref(self._h),
1206 result=self._connectionstatus,
1207 )
1208 if self._h.value != 0:
1209 __class__._connectionMap[self._h.value] = self
1210 self._process_msg(Msg.GetStageConfig, result=self._stageconfig)
1211 else:
1212 raise microscope.InitialiseError("Could not connect to stage.")
1213
1214 def _reopen_comms(self):
1215 """Reopen communications.
1216
1217 This is called by the error event callback, which runs in some thread in
1218 the library. If comms_close is called in that thread, the connection
1219 status isn't correctly updated, so reconnection must happen in another
1220 thread."""
1221 if (
1222 self._reconnect_thread is not None
1223 and self._reconnect_thread.is_alive()
1224 ):
1225 # Already trying to reconnect
1226 return
1227
1228 self._reconnect_thread = threading.Thread(
1229 target=self._reopen_loop, daemon=True
1230 )
1231 self._reconnect_thread.start()
1232
1233 def _reopen_loop(self):
1234 """Attempt to reopen comms."""
1235 self.close_comms()
1236 # Need a small delay to avoid false positive connected status.
1237 time.sleep(0.1)
1238 while not self._connectionstatus.flags.connected:
1239 time.sleep(0.5)
1240 try:
1241 self.open_comms()
1242 except:
1243 pass
1244
1245 def _update_status(self, status):
1246 """Update status structures."""
1247 self._status = status
1248
1249 def init_usb(self, uid):
1250 """Populate commsinfo struct with default USBCommsInfo"""
1251 # The uid is used to set serialNumber on the info object. The docs
1252 # suggest that an OpenComms message should open a connection to the
1253 # device with that serial number; with only one stage attached, it
1254 # appears that OpenComms ignores the value of serialNumber.
1255 if uid is None:
1256 uid = b""
1257 elif not isinstance(uid, bytes):
1258 uid = uid.encode()
1259 self._lib.linkamInitialiseUSBCommsInfo(
1260 byref(self._commsinfo), ctypes.c_char_p(uid)
1261 )
1262
1263 def init_serial(self, port):
1264 """Populate commsinfo struct with default SerialCommsInfo for given port"""
1265 self._lib.linkamInitialiseSerialCommsInfo(byref(self._commsinfo), port)
1266
1267 def get_status(self, *args):
1268 """Called by a client to fetch status in a dict.
1269
1270 Derived classes and mixins should implement this to add their own status.
1271
1272 status = super().get_status(*args, status_structure, ...) in derived classes.
1273 # then add any other values with
1274 status[key] = ...
1275 """
1276 structs = args + (self._status, self._connectionstatus)
1277 status = {}
1278 for s in structs:
1279 names = filter(
1280 lambda n: not n.startswith("unused"),
1281 (f[0] for f in s.flags._fields_),
1282 )
1283 status.update(
1284 dict(map(lambda n: (n, bool(getattr(s.flags, n))), names))
1285 )
1286 return status
1287
1288
1290 """A mixin for motor-driven stages"""
1291
1292 def __init__(self, **kwargs):
1293 super().__init__(**kwargs)
1294 self._mdsstatus = _MDSStatus()
1295
1296 def _post_connect(self):
1297 """Set up motors: set velocities and add velocity settings."""
1298 for name, flag, svt in (
1299 (
1300 "X_velocity",
1301 self._stageconfig.flags.motorX,
1302 _StageValueType.MotorVelX,
1303 ),
1304 (
1305 "Y_velocity",
1306 self._stageconfig.flags.motorY,
1307 _StageValueType.MotorVelY,
1308 ),
1309 (
1310 "Z_velocity",
1311 self._stageconfig.flags.motorZ,
1312 _StageValueType.MotorVelZ,
1313 ),
1314 ):
1315 if flag:
1316 # Motors don't move unless their velocities have been written to,
1317 # despite velocities having a non-zero power-on default. There's no
1318 # way to tell if they've been written to, so write them once here.
1319 self.set_value(svt, self.get_value(svt))
1320 # Also add a Setting that clients can use to modify the velocity.
1321 self.add_setting(
1322 name,
1323 "float",
1324 lambda svt=svt: self.get_value(svt),
1325 lambda val, svt=svt, s=self: self.set_value(svt, val),
1326 lambda svt=svt: self.get_value_limits(svt),
1327 )
1328
1329 super()._post_connect()
1330
1331 def _update_status(self, status):
1332 """Call parent class update_status, then update MDS status structure."""
1333 super()._update_status(status)
1334 self.get_value(
1335 _StageValueType.MotorDrivenStageStatus, result=self._mdsstatus
1336 )
1337
1338 def move_to(self, x=None, y=None, z=None):
1339 """Move to co-ordinates given by x and y"""
1340 # The default position set points are zero. If the motors are started without
1341 # writing to a set point, that motor will move to zero, so only start motors
1342 # if a target has been provided.
1343 if x is not None:
1344 self.set_value(_StageValueType.MotorSetpointX, x)
1345 self._process_msg(Msg.StartMotors, True, 0)
1346 if y is not None:
1347 self.set_value(_StageValueType.MotorSetpointY, y)
1348 self._process_msg(Msg.StartMotors, True, 1)
1349 if z is not None:
1350 self.set_value(_StageValueType.MotorSetpointZ, z)
1351 self._process_msg(Msg.StartMotors, True, 2)
1352 # Allow time for status structures to indicate stage is moving
1353 time.sleep(5 * self.get_data_rate())
1354
1355 def get_status(self, *args):
1356 """Includes MDSStatus in the get_status call."""
1357 return super().get_status(*args, self._mdsstatus)
1358
1359 def get_position(self):
1360 """Return the stage's position."""
1361 pos = {}
1362 for axis in "ZYX":
1363 if getattr(self._stageconfig.flags, "motor" + axis):
1364 pos[axis] = self.get_value(
1365 getattr(_StageValueType, "MotorPos" + axis)
1366 )
1367 else:
1368 pos[axis] = float("nan")
1369 return pos
1370
1371
1373 """Linkam correlative-microscopy stage."""
1374
1375 _refill_map = {
1376 "sample": "sampleDewarFillSignal",
1377 "external": "mainDewarFillSignal",
1378 }
1379 _heater_map = {
1380 "t_bridge": _StageValueType.Heater1Temp,
1381 "t_chamber": _StageValueType.Heater2Temp,
1382 "t_dewar": _StageValueType.Heater3Temp,
1383 "t_base": _StageValueType.Heater4Temp,
1384 }
1385
1387 # Is refill in progress?
1388 refilling = False
1389 # Time between last refills.
1390 dt = datetime.timedelta(0)
1391 # Last refill time.
1392 t = None
1393
1394 def start_refill(self):
1395 """Start a refill: update status flag and last cycle time."""
1396 self.refillingrefilling = True
1397 if self.t is not None:
1398 self.dt = datetime.datetime.now() - self.t
1399
1400 def end_refill(self):
1401 """End a refill: update status flag and last refill time."""
1402 self.refillingrefilling = False
1403 self.t = datetime.datetime.now()
1404
1405 def as_dict(self):
1406 """Represent this object as a dict for status queries."""
1407 return dict(
1408 refilling=self.refillingrefilling, last=self.t, between_last=self.dt
1409 )
1410
1411 def __repr__(self):
1412 """Display tracker properties in representation."""
1413 return "refilling: %s, t: %s, dt: %s" % (
1414 self.refillingrefilling,
1415 self.t,
1416 self.dt,
1417 )
1418
1419 def __init__(self, uid="", **kwargs):
1420 super().__init__(**kwargs)
1421 self.uid = uid
1422 self.init_usb(self.uid)
1423 self._cmsstatus = _CMSStatus()
1424 self._cmserror = _CMSError()
1425 # Refill tracking
1426 self._refills = dict(
1427 {k: self.RefillTracker() for k in self._refill_map}
1428 )
1429 # Condensor LED level when on
1430 self._condensor_level = 100
1431 self.add_setting(
1432 "condensor",
1433 "float",
1436 (0, 100),
1437 )
1438 # Connect to the stage.
1439 if self.uid:
1440 # Deviceserver needs uid to associate device to address, so call
1441 # open_comms directly to throw an exception on connection error.
1442 self.open_comms()
1443 else:
1444 # Device id not required - just keep trying to connect until success.
1445 self._reopen_comms()
1446
1447 def _update_status(self, status):
1448 """Update status structures."""
1449 super()._update_status(status)
1450 self.get_value(_StageValueType.CmsStatus, result=self._cmsstatus)
1451 self.get_value(_StageValueType.CmsError, result=self._cmserror)
1452 # Update the refill timers.
1453 for (key, flagname) in self._refill_map.items():
1454 tracker = self._refills[key]
1455 is_refilling = getattr(self._cmsstatus.flags, flagname)
1456 if is_refilling and not tracker.refilling:
1457 tracker.start_refill()
1458 elif self._refills[key].refilling and not is_refilling:
1459 tracker.end_refill()
1460
1461 def temperatures(self):
1462 """Return a dict of temperature sensor readings."""
1463 return dict(
1464 (key, self.get_value(svt)) for key, svt in self._heater_map.items()
1465 )
1466
1467 def set_light(self, state):
1468 """Set the state of the chamber light."""
1469 self.set_value(_StageValueType.CmsLight, state)
1470
1471 def get_light(self):
1472 """Report the state of the chamber light."""
1473 return self.get_value(_StageValueType.CmsLight)
1474
1475 def set_condensor(self, state):
1476 """Turn the condensor LED on or off."""
1477 if state:
1478 level = self._condensor_level
1479 else:
1480 level = 0
1481 self.set_value(_StageValueType.CmsCondenserLEDLevel, level)
1482
1483 def set_condensor_level(self, level):
1484 """Set the condensor LED level"""
1485 self._condensor_level = level
1486 if self.get_value(_StageValueType.CmsCondenserLEDLevel) > 0:
1487 # Condnesor LED is on, so write out new level immediately.
1488 self.set_condensor(True)
1489
1491 """Return the condensor level"""
1492 return self._condensor_level
1493
1494 def get_motors(self):
1495 """Return the position, set point and stopped status of available motors."""
1496 res = {}
1497 for axis in "XYZ":
1498 if getattr(self._stageconfig.flags, "motor" + axis):
1499 res[axis] = (
1500 self.get_value(
1501 getattr(_StageValueType, "MotorPos" + axis)
1502 ),
1503 self.get_value(
1504 getattr(_StageValueType, "MotorSetpoint" + axis)
1505 ),
1506 getattr(self._status.flags, "motorStopped" + axis),
1507 )
1508 else:
1509 res[axis] = None
1510 return res
1511
1512 def refill_dewar(self, state=True):
1513 """Start a refill of the internal dewar from an external reservoir"""
1514 return self.set_value(_StageValueType.CmsMainDewarFillSig, True)
1515
1516 def refill_chamber(self, state=True):
1517 """Start a refill of the sample chamber from the internal dewar"""
1518 return self.set_value(_StageValueType.CmsSampleDewarFillSig, True)
1519
1520 def refill_stats(self):
1521 """Return information about refill times and cycle lengths."""
1522 return dict([(k, v.as_dict()) for k, v in self._refills.items()])
1523
1524 def get_status(self, *args):
1525 """Return a dict containing aggregated stage status."""
1526 status = super().get_status(*args, self._cmsstatus)
1527 status.update(self.temperatures())
1528 status.update(refills=self.refill_stats())
1529 return status
None add_setting(self, name, dtype, get_func, set_func, values, typing.Optional[typing.Callable[[], bool]] readonly=None)
Definition: abc.py:407
def _process_msg(self, msg, param1=None, param2=None, param3=None, result=None)
Definition: linkam.py:1102
def get_value(self, svt, result=None)
Definition: linkam.py:1132
def __init__(self, **kwargs)
Definition: linkam.py:1072
def is_moving(self, axis=None)
Definition: linkam.py:1179
def set_value(self, svt, val)
Definition: linkam.py:1166
def move_to(self, x=None, y=None, z=None)
Definition: linkam.py:1338
def __getattribute__(self, name)
Definition: linkam.py:895
def set_condensor_level(self, level)
Definition: linkam.py:1483
def set_condensor(self, state)
Definition: linkam.py:1475
def refill_chamber(self, state=True)
Definition: linkam.py:1516
def refill_dewar(self, state=True)
Definition: linkam.py:1512
def __init__(self, uid="", **kwargs)
Definition: linkam.py:1419