pi_e727.py 9.7 KB
Newer Older
1
2
3
4
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
Benoit Formet's avatar
Benoit Formet committed
5
# Copyright (c) 2015-2020 Beamline Control Unit, ESRF
6
7
8
# Distributed under the GNU LGPLv3. See LICENSE for more info.

from bliss.controllers.motor import Controller
9
from bliss.common.utils import object_attribute_get, object_attribute_set, object_method
10
from bliss.common.axis import AxisState
11
from bliss.common.logtools import log_debug, log_error, log_info
12
from bliss import global_map
13

Vincent Michel's avatar
Vincent Michel committed
14
from . import pi_gcs
15
16


17
18
19
20
class PI_E727(pi_gcs.Communication, pi_gcs.Recorder, Controller):
    """
    Bliss controller for ethernet PI E727 piezo controller.
    """
21

22
    model = None
23
24

    def __init__(self, *args, **kwargs):
25
26
27
28
        # Called at session startup
        # No hardware access
        pi_gcs.Communication.__init__(self)
        pi_gcs.Recorder.__init__(self)
29
30
31
32
33
        Controller.__init__(self, *args, **kwargs)

    # Init of controller.
    def initialize(self):
        """
34
35
36
37
        Controller intialization.
        Called at session startup.
        Called Only once per controller even if more than
        one axis is declared in config.
38
        """
39
40
        # acceleration is not mandatory in config
        self.axis_settings.config_setting["acceleration"] = False
41

42
43
44
        # model of the controller.
        self.model = "E727"
        log_debug(self, "model=%s", self.model)
45

46
    def close(self):
47
        """
48
        Called at session exit. many times ???
49
        """
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
        self.com_close()

    def initialize_hardware(self):
        """
        Called once per controller at first use of any of the axes.

        NB: not initialized if accessing to the controller before the axis:
           ex: LILAB [1]: napix.controller.command("CCL 1 advanced")
               !!! === AttributeError: 'NoneType' object has no attribute 'lock' === !!!
        """
        # Initialize socket communication.
        self.com_initialize()

    def initialize_hardware_axis(self, axis):
        """
        Called once per axis at first use of the axis
        """
        pass
68

69
70
71
72
    def initialize_axis(self, axis):
        """
        Called at first access to <axis> (eg: __info__())
        """
73
74
75
        axis.channel = axis.config.get("channel", int)

        # check communication
76
        _ans = self.get_identifier(axis)
77
78

        # Enables the closed-loop.
79
        # To be imporved.
80
81
        self._set_closed_loop(axis, True)

82
83
84
        # supposed that we are on target on init
        axis._last_on_target = True

85
86
87
88
89
90
    def initialize_encoder(self, encoder):
        pass

    """
    ON / OFF
    """
91

92
93
94
95
96
97
    def set_on(self, axis):
        pass

    def set_off(self, axis):
        pass

98
99
100
101
    """
    POSITION
    """

102
    def read_position(self, axis):
103
104
105
106
107
108
109
110
111
112
113
        """
        Return position of the axis.
        After movement is finished, return the target position.
        """
        if axis._last_on_target:
            _pos = self._get_target_pos(axis)
            log_debug(self, "position read : %g" % _pos)
        else:
            # if moving return real position
            _pos = self._get_pos(axis)
        return _pos
114
115

    def read_encoder(self, encoder):
116
        _ans = self._get_pos(encoder.axis)
117
        log_debug(self, "read_position measured = %f" % _ans)
118
119
        return _ans

120
121
122
    """
    VELOCITY
    """
123

124
125
126
127
    def read_velocity(self, axis):
        return self._get_velocity(axis)

    def set_velocity(self, axis, new_velocity):
128
        log_debug(self, "set_velocity new_velocity = %f" % new_velocity)
129
        _cmd = "VEL %s %f" % (axis.channel, new_velocity)
130
        self.command(_cmd)
131
132
133

        return self.read_velocity(axis)

134
135
136
    """
    STATE
    """
137

138
    def state(self, axis):
139
        # self.trace("axis state")
140
141
142
143
144
145
146
147
        if self._get_closed_loop_status(axis):
            if self._get_on_target_status(axis):
                return AxisState("READY")
            else:
                return AxisState("MOVING")
        else:
            raise RuntimeError("closed loop disabled")

148
149
150
    """
    MOVEMENTS
    """
151

152
    def prepare_move(self, motion):
Manuel Perez's avatar
Manuel Perez committed
153
        pass
154
155

    def start_one(self, motion):
156
        log_debug(self, "start_one target_pos = %f" % motion.target_pos)
157
158

        axis = motion.axis
159
160
        _cmd = f"MOV {axis.channel} {motion.target_pos}"
        self.command(_cmd)
161
162

    def stop(self, axis):
163
164
165
166
167
168
169
170
171
172
        """
        * STP -> stop asap
        * 24  -> stop asap
        * HLT -> stop smoothly
        * Copy of current position into target position at stop
        * all 3 commands generate (by default) a 'Disable Error 10'.
          (This can be disabled via parameter 0x0E000301)
        """
        log_debug(self, "stop (HLT) requested")
        self.command("HLT %s" % (axis.channel))
173
174
175
176

    """
    E727 specific
    """
177

178
    @object_method(types_info=("None", "str"))
179
180
    def get_identifier(self, axis):
        return self.command("IDN?")
181

182
    @object_method(types_info=("None", "float"))
183
    def get_voltage(self, axis):
184
        """ Return voltage read from controller."""
185
186
        _ans = self.command(f"SVA? {axis.channel}")
        _voltage = float(_ans)
187
188
        return _voltage

189
190
191
192
193
    @object_method(types_info=("None", "float"))
    def get_output_voltage(self, axis):
        """ Return output voltage read from controller. """
        return float(self.command(f"VOL? {axis.channel}"))

194
    def set_voltage(self, axis, new_voltage):
195
        """ Set Voltage to the controller."""
196
        _cmd = "SVA %s %g" % (axis.channel, new_voltage)
197
        self.command(_cmd)
198
199
200

    def _get_velocity(self, axis):
        """
201
        Return velocity taken from controller.
202
        """
203
204
        _ans = self.command(f"VEL? {axis.channel}")
        _velocity = float(_ans)
205
206
207
208
209

        return _velocity

    def _get_pos(self, axis):
        """
210
        Return real position read by capacitive sensor.
211
        """
212
213
        _ans = self.command(f"POS? {axis.channel}")
        _pos = float(_ans)
214
215
        return _pos

216
217
218
    """
    CLOSED LOOP
    """
219

220
221
    def _get_target_pos(self, axis):
        """
222
        Return last target position (setpoint value).
223
        """
224
225
        _ans = self.command(f"MOV? {axis.channel}")
        _pos = float(_ans)
226
227
228
        return _pos

    def _get_on_target_status(self, axis):
229
230
231
232
233
234
235
236
        """
        Return 'On-target status' (ONT? command) indicating
        if movement is finished and measured position is within
        on-target window.
        """
        last_on_target = bool(int(self.command(f"ONT? {axis.channel}")))
        axis._last_on_target = last_on_target
        return last_on_target
237

238
    def _get_closed_loop_status(self, axis):
239
        _status = self.command(f"SVO? {axis.channel}")
240
241
242

        if _status == "1":
            return True
243
244

        if _status == "0":
245
            return False
246
247
248

        log_error(self, "ERROR on _get_closed_loop_status, _status=%r" % _status)
        return -1
249
250
251

    def _set_closed_loop(self, axis, state):
        if state:
252
            _cmd = f"SVO {axis.channel} 1"
253
        else:
254
255
            _cmd = f"SVO {axis.channel} 0"
        self.command(_cmd)
256
257
258
259
260
261
262
263
264

    @object_method(types_info=("None", "None"))
    def open_loop(self, axis):
        self._set_closed_loop(axis, False)

    @object_method(types_info=("None", "None"))
    def close_loop(self, axis):
        self._set_closed_loop(axis, True)

265
266
267
    @object_attribute_get(type_info="bool")
    def get_closed_loop(self, axis):
        return self._get_closed_loop_status(axis)
268

269
270
271
    """
    INFO
    """
272

273
274
275
    @object_attribute_get(type_info="str")
    def get_model(self, axis):
        return self.model
276

277
278
279
280
281
    def get_id(self, axis):
        """
        Return controller identifier.
        """
        return self.command("*IDN?")
282

283
284
285
286
287
288
289
290
291
    def get_axis_info(self, axis):
        """
        Return Controller specific info about <axis> for user.
        Detailed infos are in get_info().
        """
        info_str = "PI AXIS INFO:\n"
        info_str += f"     voltage (SVA) = {self.get_voltage(axis)}\n"
        info_str += f"     output voltage (VOL) = {self.get_output_voltage(axis)}\n"
        info_str += f"     closed loop = {self.get_closed_loop(axis)}\n"
292

293
294
295
296
297
298
299
300
301
302
        return info_str

    def __info__(self):
        info_str = "CONTROLLER:\n"

        (_err_nb, _err_str) = self.get_error()
        info_str = f"Last Error: {_err_nb} ({_err_str})\n"
        info_str += "COMMUNICATION CONFIG:\n     "
        info_str += self.sock.__info__()
        return info_str
303

304
    @object_method(types_info=("None", "string"))
305
306
    def get_info(self, axis):
        """
307
        Return detailed information about controller.
308
309
310
311
312
313
314
315
        Helpful to tune the device.
        """
        axis.position  # force axis initialization

        _tab = 30
        _txt = ""

        # use command "HPA?" to get parameters address + description
316
317
318
319

        # NB: 0x7000000 used and not 0x07000000
        #     because PI 727 remove first 0 in the command answer
        #     -> cause problem in answer check.
320
        _infos = [
321
322
323
324
325
326
327
328
329
            ("Real Position", "POS? %s"),
            ("Setpoint Position", "MOV? %s"),
            ("On target", "ONT? %s"),
            ("Velocity", "VEL? %s"),
            ("Closed loop status", "SVO? %s"),
            ("Auto Zero Calibration ?", "ATZ? %s"),
            ("Analog input setpoint", "AOS? %s"),
            ("ADC value of analog input", "TAD? %s"),
            ("Analog setpoints", "TSP? %s"),
330
331
332
333
334
335
            ("AutoZero Low Voltage", "SPA? %s 0x7000A00"),
            ("AutoZero High Voltage", "SPA? %s 0x7000A01"),
            ("Range Limit min", "SPA? %s 0x7000000"),
            ("Range Limit max", "SPA? %s 0x7000001"),
            ("ON Target Tolerance", "SPA? %s 0x7000900"),
            ("Settling time", "SPA? %s 0X7000901"),
336
337
338
339
340
341
        ]

        for i in _infos:
            _cmd = i[1]
            if "%s" in _cmd:
                _cmd = _cmd % (axis.channel)
342
            _ans = self.command(_cmd)
343
344
345
            _txt = _txt + "%*s: %s\n" % (_tab, i[0], _ans)

        return _txt