Skip to content

Commit 7485d60

Browse files
committed
ObisLaser: merge support for Coherent Obis laser (issue #55)
2 parents a285564 + 1ce7947 commit 7485d60

File tree

2 files changed

+192
-0
lines changed

2 files changed

+192
-0
lines changed

doc/supported-devices.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,5 @@ Lasers
3737

3838
- Cobolt (:class:`microscope.lasers.cobolt`)
3939
- Coherent Sapphire (:class:`microscope.lasers.sapphire`)
40+
- Coherent Obis (:class:`microscope.lasers.obis`)
4041
- Omicron Deepstar (:class:`microscope.lasers.deepstar`)

microscope/lasers/obis.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8
3+
4+
# Copyright 2016 Mick Phillips (mick.phillips@gmail.com)
5+
# Copyright 2019 David Miguel Susano Pinto <david.pinto@bioch.ox.ac.uk>
6+
# Copyright 2018 Julio Mateos Langerak <julio.mateos-langerak@igh.cnrs.fr>
7+
#
8+
# This program is free software: you can redistribute it and/or modify
9+
# it under the terms of the GNU General Public License as published by
10+
# the Free Software Foundation, either version 3 of the License, or
11+
# (at your option) any later version.
12+
#
13+
# This program is distributed in the hope that it will be useful,
14+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
# GNU General Public License for more details.
17+
#
18+
# You should have received a copy of the GNU General Public License
19+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
21+
import logging
22+
23+
import serial
24+
25+
from microscope import devices
26+
27+
28+
_logger = logging.getLogger(__name__)
29+
30+
31+
class ObisLaser(devices.SerialDeviceMixIn, devices.LaserDevice):
32+
def __init__(self, com, baud=115200, timeout=0.5, **kwargs) -> None:
33+
super().__init__(**kwargs)
34+
self.connection = serial.Serial(port=com, baudrate=baud,
35+
timeout=timeout,
36+
stopbits=serial.STOPBITS_ONE,
37+
bytesize=serial.EIGHTBITS,
38+
parity=serial.PARITY_NONE)
39+
# Start a logger.
40+
self._write(b'SYSTem:INFormation:MODel?')
41+
response = self._readline()
42+
_logger.info('OBIS laser model: [%s]' % response.decode())
43+
self._write(b'SYSTem:INFormation:SNUMber?')
44+
response = self._readline()
45+
_logger.info('OBIS laser serial number: [%s]' % response.decode())
46+
self._write(b'SYSTem:CDRH?')
47+
response = self._readline()
48+
_logger.info('CDRH safety: [%s]' % response.decode())
49+
self._write(b'SOURce:TEMPerature:APRobe?')
50+
response = self._readline()
51+
_logger.info('TEC temperature control: [%s]' % response.decode())
52+
self._write(b'*TST?')
53+
response = self._readline()
54+
_logger.info('Self test procedure: [%s]' % response.decode())
55+
56+
# We need to ensure that autostart is disabled so that we can
57+
# switch emission on/off remotely.
58+
self._write(b'SYSTem:AUTostart?')
59+
response = self._readline()
60+
_logger.info('Response to Autostart: [%s]' % response.decode())
61+
62+
def _write(self, command):
63+
"""Send a command."""
64+
response = self.connection.write(command + b'\r\n')
65+
return response
66+
67+
def _readline(self):
68+
"""Read a line from connection without leading and trailing whitespace.
69+
We override from serialDeviceMixIn
70+
"""
71+
response = self.connection.readline().strip()
72+
if self.connection.readline().strip() != b'OK':
73+
raise Exception('Did not get a proper answer from the laser serial comm.')
74+
return response
75+
76+
def _flush_handshake(self):
77+
self.connection.readline()
78+
79+
@devices.SerialDeviceMixIn.lock_comms
80+
def get_status(self):
81+
result = []
82+
for cmd, stat in [(b'SOURce:AM:STATe?', 'Emission on?'),
83+
(b'SOURce:POWer:LEVel:IMMediate:AMPLitude?', 'Target power:'),
84+
(b'SOURce:POWer:LEVel?', 'Measured power:'),
85+
(b'SYSTem:STATus?', 'Status code?'),
86+
(b'SYSTem:FAULt?', 'Fault code?'),
87+
(b'SYSTem:HOURs?', 'Head operating hours:')]:
88+
self._write(cmd)
89+
result.append(stat + ' ' + self._readline().decode())
90+
return result
91+
92+
@devices.SerialDeviceMixIn.lock_comms
93+
def enable(self):
94+
"""Turn the laser ON. Return True if we succeeded, False otherwise."""
95+
_logger.info('Turning laser ON.')
96+
# Exiting Sleep Mode.
97+
self._write(b'SOURce:TEMPerature:APRobe ON')
98+
self._flush_handshake()
99+
# Turn on emission.
100+
self._write(b'SOURce:AM:STATe ON')
101+
self._flush_handshake()
102+
self._write(b'SOURce:AM:STATe?')
103+
response = self._readline()
104+
_logger.info("SOURce:AM:STATe? [%s]" % response.decode())
105+
106+
if not self.get_is_on():
107+
# Something went wrong.
108+
_logger.error("Failed to turn ON. Current status:\r\n")
109+
_logger.error(self.get_status())
110+
return False
111+
return True
112+
113+
def _on_shutdown(self):
114+
self.disable()
115+
# We set the power to a safe level
116+
self._set_power_mw(2)
117+
# We want it back into direct control mode.
118+
self._write(b'SOURce:AM:INTernal CWP')
119+
self._flush_handshake()
120+
121+
# Going into Sleep mode
122+
self._write(b'SOURce:TEMPerature:APRobe OFF')
123+
self._flush_handshake()
124+
125+
126+
def initialize(self):
127+
# self.flush_buffer()
128+
# We ensure that handshaking is off.
129+
self._write(b'SYSTem:COMMunicate:HANDshaking ON')
130+
self._flush_handshake()
131+
# We don't want 'direct control' mode.
132+
# TODO: Change to MIXED when analogue output is available
133+
self._write(b'SOURce:AM:EXTernal DIGital')
134+
self._flush_handshake()
135+
136+
@devices.SerialDeviceMixIn.lock_comms
137+
def disable(self):
138+
"""Turn the laser OFF. Return True if we succeeded, False otherwise."""
139+
_logger.info('Turning laser OFF.')
140+
# Turning LASER OFF
141+
self._write(b'SOURce:AM:STATe OFF')
142+
self._flush_handshake()
143+
144+
if self.get_is_on():
145+
_logger.error("Failed to turn OFF. Current status:\r\n")
146+
_logger.error(self.get_status())
147+
return False
148+
return True
149+
150+
@devices.SerialDeviceMixIn.lock_comms
151+
def is_alive(self):
152+
self._write(b'*IDN?')
153+
reply = self._readline()
154+
# 'Coherent, Inc-<model name>-<firmware version>-<firmware date>'
155+
return reply.startswith(b'Coherent, Inc-')
156+
157+
@devices.SerialDeviceMixIn.lock_comms
158+
def get_is_on(self):
159+
"""Return True if the laser is currently able to produce light."""
160+
self._write(b'SOURce:AM:STATe?')
161+
response = self._readline()
162+
_logger.info("Are we on? [%s]", response.decode())
163+
return response == b'ON'
164+
165+
@devices.SerialDeviceMixIn.lock_comms
166+
def get_min_power_mw(self):
167+
self._write(b'SOURce:POWer:LIMit:LOW?')
168+
power_w = self._readline()
169+
return float(power_w.decode()) * 1000.0
170+
171+
@devices.SerialDeviceMixIn.lock_comms
172+
def get_max_power_mw(self):
173+
"""Gets the maximum laser power in mW."""
174+
self._write(b'SOURce:POWer:LIMit:HIGH?')
175+
power_w = self._readline()
176+
return float(power_w.decode()) * 1000.0
177+
178+
@devices.SerialDeviceMixIn.lock_comms
179+
def get_power_mw(self):
180+
if not self.get_is_on():
181+
return 0.0
182+
self._write(b'SOURce:POWer:LEVel?')
183+
response = self._readline()
184+
return float(response.decode()) * 1000.0
185+
186+
@devices.SerialDeviceMixIn.lock_comms
187+
def _set_power_mw(self, mw):
188+
power_w = mw / 1000.0
189+
_logger.info("Setting laser power to %.7sW", power_w)
190+
self._write(b'SOURce:POWer:LEVel:IMMediate:AMPLitude %.5f' % power_w)
191+
self._flush_handshake()

0 commit comments

Comments
 (0)