Skip to content

Commit 3dd521e

Browse files
juliomateoslangerakcarandraug
authored andcommitted
ObisLaser: add support for Coherent Obis lasers (issue #55)
1 parent a285564 commit 3dd521e

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-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: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8
3+
#
4+
# Copyright 2016 Mick Phillips (mick.phillips@gmail.com)
5+
# Copyright 2018 David 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 serial
22+
23+
import Pyro4
24+
25+
from microscope import devices
26+
27+
28+
@Pyro4.expose
29+
class ObisLaser(devices.SerialDeviceMixIn, devices.LaserDevice):
30+
def __init__(self, com, baud, timeout, *args, **kwargs):
31+
super(ObisLaser, self).__init__(*args, **kwargs)
32+
self.connection = serial.Serial(port=com,
33+
baudrate=baud,
34+
timeout=timeout,
35+
stopbits=serial.STOPBITS_ONE,
36+
bytesize=serial.EIGHTBITS,
37+
parity=serial.PARITY_NONE)
38+
# Start a logger.
39+
self._write(b'SYSTem:INFormation:MODel?')
40+
response = self._readline()
41+
self._logger.info('OBIS laser model: [%s]' % response.decode())
42+
self._write(b'SYSTem:INFormation:SNUMber?')
43+
response = self._readline()
44+
self._logger.info('OBIS laser serial number: [%s]' % response.decode())
45+
self._write(b'SYSTem:CDRH?')
46+
response = self._readline()
47+
self._logger.info('CDRH safety: [%s]' % response.decode())
48+
self._write(b'SOURce:TEMPerature:APRobe?')
49+
response = self._readline()
50+
self._logger.info('TEC temperature control: [%s]' % response.decode())
51+
self._write(b'*TST?')
52+
response = self._readline()
53+
self._logger.info('Self test procedure: [%s]' % response.decode())
54+
55+
# We need to ensure that autostart is disabled so that we can switch emission
56+
# on/off remotely.
57+
self._write(b'SYSTem:AUTostart?')
58+
response = self._readline()
59+
self._logger.info('Response to Autostart: [%s]' % response.decode())
60+
61+
def _write(self, command):
62+
"""Send a command."""
63+
response = self.connection.write(command + b'\r\n')
64+
return response
65+
66+
def _readline(self):
67+
"""Read a line from connection without leading and trailing whitespace.
68+
We override from serialDeviceMixIn
69+
"""
70+
response = self.connection.readline().strip()
71+
if self.connection.readline().strip() != b'OK':
72+
raise Exception('Did not get a proper answer from the laser serial comm.')
73+
return response
74+
75+
def _flush_handshake(self):
76+
self.connection.readline()
77+
78+
@devices.SerialDeviceMixIn.lock_comms
79+
def get_status(self):
80+
result = []
81+
for cmd, stat in [(b'SOURce:AM:STATe?', 'Emission on?'),
82+
(b'SOURce:POWer:LEVel:IMMediate:AMPLitude?', 'Target power:'),
83+
(b'SOURce:POWer:LEVel?', 'Measured power:'),
84+
(b'SYSTem:STATus?', 'Status code?'),
85+
(b'SYSTem:FAULt?', 'Fault code?'),
86+
(b'SYSTem:HOURs?', 'Head operating hours:')]:
87+
self._write(cmd)
88+
result.append(stat + ' ' + self._readline().decode())
89+
return result
90+
91+
@devices.SerialDeviceMixIn.lock_comms
92+
def enable(self):
93+
"""Turn the laser ON. Return True if we succeeded, False otherwise."""
94+
self._logger.info('Turning laser ON.')
95+
# Exiting Sleep Mode.
96+
self._write(b'SOURce:TEMPerature:APRobe ON')
97+
self._flush_handshake()
98+
# Turn on emission.
99+
self._write(b'SOURce:AM:STATe ON')
100+
self._flush_handshake()
101+
self._write(b'SOURce:AM:STATe?')
102+
response = self._readline()
103+
self._logger.info("SOURce:AM:STATe? [%s]" % response.decode())
104+
105+
if not self.get_is_on():
106+
# Something went wrong.
107+
self._logger.error("Failed to turn ON. Current status:\r\n")
108+
self._logger.error(self.get_status())
109+
return False
110+
return True
111+
112+
def _on_shutdown(self):
113+
self.disable()
114+
# We set the power to a safe level
115+
self._set_power_mw(2)
116+
# We want it back into direct control mode.
117+
self._write(b'SOURce:AM:INTernal CWP')
118+
self._flush_handshake()
119+
120+
# Going into Sleep mode
121+
self._write(b'SOURce:TEMPerature:APRobe OFF')
122+
self._flush_handshake()
123+
124+
125+
def initialize(self):
126+
"""Initialization to do when cockpit connects."""
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+
self._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+
# Something went wrong.
146+
self._logger.error("Failed to turn OFF. Current status:\r\n")
147+
self._logger.error(self.get_status())
148+
return False
149+
return True
150+
151+
@devices.SerialDeviceMixIn.lock_comms
152+
def isAlive(self):
153+
return self.get_is_on
154+
155+
@devices.SerialDeviceMixIn.lock_comms
156+
def get_is_on(self):
157+
"""Return True if the laser is currently able to produce light."""
158+
self._write(b'SOURce:AM:STATe?')
159+
response = self._readline()
160+
self._logger.info("Are we on? [%s]", response.decode())
161+
return response == b'ON'
162+
163+
@devices.SerialDeviceMixIn.lock_comms
164+
def _set_power(self, power_w):
165+
"""Sets the power level in Watts"""
166+
if power_w > (self.get_max_power_mw() / 1000):
167+
return
168+
self._logger.info("Setting laser power to %.7sW", power_w)
169+
self._write(b'SOURce:POWer:LEVel:IMMediate:AMPLitude %.5f' % power_w)
170+
self._flush_handshake()
171+
curr_power = self._get_power()
172+
self._logger.info("Power response [%s]", curr_power)
173+
return curr_power
174+
175+
@devices.SerialDeviceMixIn.lock_comms
176+
def get_max_power_mw(self):
177+
"""Gets the maximum laser power in mW."""
178+
self._write(b'SYSTem:INFormation:POWer?')
179+
power_w = self._readline()
180+
return int(float(power_w.decode()) * 1000)
181+
182+
@devices.SerialDeviceMixIn.lock_comms
183+
def _get_power(self):
184+
if not self.get_is_on():
185+
# Laser is not on.
186+
return 0
187+
self._write(b'SOURce:POWer:LEVel?')
188+
response = self._readline()
189+
return float(response.decode())
190+
191+
def get_power_mw(self):
192+
return 1000 * self._get_power()
193+
194+
def _set_power_mw(self, mw):
195+
mw = min(mw, self.get_max_power_mw())
196+
return self._set_power(mw / 1000)

0 commit comments

Comments
 (0)