source: trunk/papywizard/plugins/merlinOrionPlugins.py @ 2370

Revision 2370, 11.9 KB checked in by fma, 3 years ago (diff)

Added alternate drive angle param

  • Property svn:keywords set to Id
Line 
1# -*- coding: utf-8 -*-
2
3""" Panohead remote control.
4
5License
6=======
7
8 - B{Papywizard} (U{http://www.papywizard.org}) is Copyright:
9  - (C) 2007-2010 Frédéric Mantegazza
10
11This software is governed by the B{CeCILL} license under French law and
12abiding by the rules of distribution of free software.  You can  use,
13modify and/or redistribute the software under the terms of the CeCILL
14license as circulated by CEA, CNRS and INRIA at the following URL
15U{http://www.cecill.info}.
16
17As a counterpart to the access to the source code and  rights to copy,
18modify and redistribute granted by the license, users are provided only
19with a limited warranty  and the software's author,  the holder of the
20economic rights,  and the successive licensors  have only  limited
21liability.
22
23In this respect, the user's attention is drawn to the risks associated
24with loading,  using,  modifying and/or developing or reproducing the
25software by the user in light of its specific status of free software,
26that may mean  that it is complicated to manipulate,  and  that  also
27therefore means  that it is reserved for developers  and  experienced
28professionals having in-depth computer knowledge. Users are therefore
29encouraged to load and test the software's suitability as regards their
30requirements in conditions enabling the security of their systems and/or
31data to be ensured and,  more generally, to use and operate it in the
32same conditions as regards security.
33
34The fact that you are presently reading this means that you have had
35knowledge of the CeCILL license and that you accept its terms.
36
37Module purpose
38==============
39
40Hardware
41
42Implements
43==========
44
45- MerlinOrionHardware
46- MerlinOrionAxis
47- MerlinOrionAxisController
48- MerlinOrionShutter
49- MerlinOrionShutterController
50
51@author: Frédéric Mantegazza
52@copyright: (C) 2007-2010 Frédéric Mantegazza
53@license: CeCILL
54@todo: add private methods to MerlinOrionHardware for sending commands to MerlinOrion
55"""
56
57__revision__ = "$Id$"
58
59import time
60import threading
61
62from PyQt4 import QtCore, QtGui
63
64from papywizard.common import config
65from papywizard.common.configManager import ConfigManager
66from papywizard.common.loggingServices import Logger
67from papywizard.hardware.merlinOrionHardware import MerlinOrionHardware
68from papywizard.plugins.pluginsManager  import PluginsManager
69from papywizard.plugins.abstractAxisPlugin import AbstractAxisPlugin
70from papywizard.plugins.abstractStandardShutterPlugin import AbstractStandardShutterPlugin
71from papywizard.plugins.abstractHardwarePlugin import AbstractHardwarePlugin
72from papywizard.plugins.axisPluginController import AxisPluginController
73from papywizard.plugins.hardwarePluginController import HardwarePluginController
74from papywizard.plugins.standardShutterPluginController import StandardShutterPluginController
75from papywizard.view.pluginFields import SpinBoxField, DoubleSpinBoxField, CheckBoxField
76
77NAME = "Merlin-Orion"
78
79DEFAULT_ALTERNATE_DRIVE = True
80DEFAULT_ALTERNATE_DRIVE_ANGLE = 7. # °
81DEFAULT_INERTIA_ANGLE = 1. # °
82
83TAB_HARD = unicode(QtGui.QApplication.translate("merlinOrionPlugins", 'Hard'))
84LABEL_ALTERNATE_DRIVE = unicode(QtGui.QApplication.translate("merlinOrionPlugins", "Alternate drive"))
85LABEL_ALTERNATE_DRIVE_ANGLE = unicode(QtGui.QApplication.translate("merlinOrionPlugins", "Alternate drive angle"))
86LABEL_INERTIA_ANGLE = unicode(QtGui.QApplication.translate("merlinOrionPlugins", "Inertia angle"))
87
88AXIS_ACCURACY = 0.1 # °
89AXIS_TABLE = {'yawAxis': 1,
90              'pitchAxis': 2,
91              'shutter': 1
92              }
93MANUAL_SPEED_TABLE = {'slow': 170,  # "AA0000"  / 5
94                      'alternate': 80, # "500000"
95                      'normal': 34, # "220000" nominal
96                      'fast': 17   # "110000"  * 2
97                      }
98
99
100class MerlinOrionAxis(AbstractHardwarePlugin, AbstractAxisPlugin, QtCore.QThread):
101    """
102    """
103    def __init__(self, *args, **kwargs):
104        AbstractHardwarePlugin.__init__(self, *args, **kwargs)  # Only 1?
105        AbstractAxisPlugin.__init__(self, *args, **kwargs)
106        QtCore.QThread.__init__(self)
107
108    def _init(self):
109        Logger().trace("MerlinOrionAxis._init()")
110        AbstractHardwarePlugin._init(self)
111        AbstractAxisPlugin._init(self)
112        self._hardware = MerlinOrionHardware()
113        self.__run = False
114        self.__driveFlag = False
115        self.__setPoint = None
116
117    def _defineConfig(self):
118        AbstractAxisPlugin._defineConfig(self)
119        AbstractHardwarePlugin._defineConfig(self)
120        self._addConfigKey('_alternateDrive', 'ALTERNATE_DRIVE', default=DEFAULT_ALTERNATE_DRIVE)
121        self._addConfigKey('_alternateDrive', 'ALTERNATE_DRIVE_ANGLE', default=DEFAULT_ALTERNATE_DRIVE_ANGLE)
122        self._addConfigKey('_inertiaAngle', 'INERTIA_ANGLE', default=DEFAULT_INERTIA_ANGLE)
123
124    def activate(self):
125        Logger().trace("MerlinOrionHardware.activate()")
126        AbstractAxisPlugin.activate(self)
127
128        # Start the thread
129        self.start()
130
131    def deactivate(self):
132        Logger().trace("MerlinOrionHardware.deactivate()")
133
134        # Stop the thread
135        self._stopThread()
136        self.wait()
137        AbstractAxisPlugin.deactivate(self)
138
139    def init(self):
140        Logger().trace("MerlinOrionAxis.init()")
141        self._hardware.setAxis(AXIS_TABLE[self.capacity]),
142        AbstractHardwarePlugin.init(self)
143
144    def shutdown(self):
145        Logger().trace("MerlinOrionAxis.shutdown()")
146        self.stop()
147        AbstractHardwarePlugin.shutdown(self)
148        AbstractAxisPlugin.shutdown(self)
149
150    def run(self):
151        """ Main entry of the thread.
152        """
153        threadName = "%s_%s" % (self.name, self.capacity)
154        threading.currentThread().setName(threadName)
155        Logger().debug("MerlinOrionAxis.run(): start thread")
156        self.__run = True
157        while self.__run:
158            if self.__driveFlag:
159
160                # Choose alternate drive if needed
161                currentPos = self.read()
162                if self._config['ALTERNATE_DRIVE'] and \
163                   1.1 * self._config['INERTIA_ANGLE'] < abs(self.__setPoint - currentPos) < self._config['ALTERNATE_DRIVE_ANGLE']:
164                    self._alternateDrive(self.__setPoint)
165                else:
166                    self._directDrive(self.__setPoint)
167                self.__driveFlag = False
168                self.waitEndOfDrive()  # ???
169
170            self.msleep(config.SPY_REFRESH_DELAY)
171
172        Logger().debug("MerlinOrionAxis.run(): thread terminated")
173
174    def _stopThread(self):
175        """ Stop the thread.
176        """
177        self.__run = False
178
179    def read(self):
180        pos = self._hardware.read() - self._offset
181        return pos
182
183    def drive(self, pos, useOffset=True, wait=True):
184        Logger().debug("MerlinOrionAxis.drive(): '%s' drive to %.1f" % (self.capacity, pos))
185        currentPos = self.read()
186
187        self._checkLimits(pos)
188
189        # Only move if needed
190        if abs(pos - currentPos) > AXIS_ACCURACY or not useOffset:
191            if useOffset:
192                Logger().debug("MerlinOrionAxis.drive(): offset=%.1f" % self._offset)
193                pos += self._offset
194
195            self.__setPoint = pos
196            self.__driveFlag = True # Start thread action
197
198            # Wait end of movement
199            if wait:
200                self.waitEndOfDrive()
201
202    def _directDrive(self, pos):
203        """ Default (hardware) drive.
204
205        @param pos: position to reach, in °
206        @type pos: float
207        """
208        Logger().trace("MerlinOrionAxis._directDrive()")
209        self._hardware.drive(pos)
210
211    def _alternateDrive(self, pos):
212        """ Alternate drive.
213
214        This method implements an external closed-loop regulation.
215        It is faster for angles < 6-7°, because in this case, the
216        head does not accelerate to full speed, but rather stays at
217        very low speed.
218
219        @param pos: position to reach, in °
220        @type pos: float
221        """
222        Logger().trace("MerlinOrionAxis._alternateDrive()")
223
224        # Compute initial direction
225        currentPos = self.read()
226        if pos > currentPos:
227            dir_ = '+'
228        else:
229            dir_ = '-'
230        stopRequest = False
231
232        # Alternate speed move
233        Logger().debug("MerlinOrionAxis._alternateDrive(): alternate speed move")
234        self._hardware.startJog(dir_, MANUAL_SPEED_TABLE['alternate'])
235
236        # Check when stop
237        while abs(pos - self.read()) > self._config['INERTIA_ANGLE']:
238
239            # Test if a stop request has been sent
240            if not self.isMoving():
241                stopRequest = True
242                break
243            time.sleep(config.SPY_REFRESH_DELAY / 1000.)
244        self._hardware.stop()
245
246        # Final move
247        if abs(pos - self.read()) > AXIS_ACCURACY and not stopRequest:
248            Logger().debug("MerlinOrionAxis._alternateDrive(): final move")
249            self._hardware.drive(pos)
250
251    def waitEndOfDrive(self):
252        while self.isMoving():
253            time.sleep(config.SPY_REFRESH_DELAY / 1000.)
254        self.waitStop()
255
256    def startJog(self, dir_):
257        self._hardware.startJog(dir_, MANUAL_SPEED_TABLE[self._manualSpeed])
258
259    def stop(self):
260        self.__driveFlag = False
261        self._hardware.stop()
262        self.waitStop()
263
264    def waitStop(self):
265        pos = self.read()
266        time.sleep(config.SPY_REFRESH_DELAY / 1000.)
267        while True:
268            if abs(pos - self.read()) <= AXIS_ACCURACY:
269                break
270            pos = self.read()
271            time.sleep(config.SPY_REFRESH_DELAY / 1000.)
272
273    def isMoving(self):
274        status = self._hardware.getStatus()
275        if status[1] != '0' or self.__driveFlag:
276            return True
277        else:
278            return False
279
280
281class MerlinOrionAxisController(AxisPluginController, HardwarePluginController):
282    def _defineGui(self):
283        AxisPluginController._defineGui(self)
284        HardwarePluginController._defineGui(self)
285        self._addTab('Hard', TAB_HARD)
286        self._addWidget('Hard', LABEL_ALTERNATE_DRIVE, CheckBoxField, (), 'ALTERNATE_DRIVE')
287        self._addWidget('Hard', LABEL_ALTERNATE_DRIVE_ANGLE, SpinBoxField, (3, 15, "", u" °"), 'ALTERNATE_DRIVE_ANGLE')
288        self._addWidget('Hard', LABEL_INERTIA_ANGLE, DoubleSpinBoxField, (0.1, 9.9, 1, .1, "", u" °"), 'INERTIA_ANGLE')
289
290
291class MerlinOrionShutter(AbstractHardwarePlugin, AbstractStandardShutterPlugin):
292    def __init__(self, *args, **kwargs):
293        """
294        """
295        AbstractHardwarePlugin.__init__(self, *args, **kwargs)
296        AbstractStandardShutterPlugin.__init__(self, *args, **kwargs)
297
298    def _init(self):
299        Logger().trace("MerlinOrionShutter._init()")
300        AbstractHardwarePlugin._init(self)
301        AbstractStandardShutterPlugin._init(self)
302        self._hardware = MerlinOrionHardware()
303
304    def _defineConfig(self):
305        AbstractHardwarePlugin._defineConfig(self)
306        AbstractStandardShutterPlugin._defineConfig(self)
307
308    def _triggerOnShutter(self):
309        """ Set the shutter on.
310        """
311        self._hardware.setOutput(True)
312
313    def _triggerOffShutter(self):
314        """ Set the shutter off.
315        """
316        self._hardware.setOutput(False)
317
318    def init(self):  # Move to AbstractHardwarePlugin?
319        Logger().trace("MerlinOrionShutter.init()")
320        self._hardware.setAxis(AXIS_TABLE[self.capacity]),
321        AbstractHardwarePlugin.init(self)
322
323    def shutdown(self):
324        Logger().trace("MerlinOrionShutter.shutdown()")
325        self._triggerOffShutter()
326        AbstractHardwarePlugin.shutdown(self)
327        AbstractStandardShutterPlugin.shutdown(self)
328
329
330class MerlinOrionShutterController(StandardShutterPluginController, HardwarePluginController):
331    def _defineGui(self):
332        StandardShutterPluginController._defineGui(self)
333        HardwarePluginController._defineGui(self)
334
335
336def register():
337    """ Register plugins.
338    """
339    PluginsManager().register(MerlinOrionAxis, MerlinOrionAxisController, capacity='yawAxis', name=NAME)
340    PluginsManager().register(MerlinOrionAxis, MerlinOrionAxisController, capacity='pitchAxis', name=NAME)
341    PluginsManager().register(MerlinOrionShutter, MerlinOrionShutterController, capacity='shutter', name=NAME)
Note: See TracBrowser for help on using the repository browser.