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

Revision 2355, 11.6 KB checked in by fma, 3 years ago (diff)

Fixed bug when offset

  • 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 DoubleSpinBoxField, CheckBoxField
76
77NAME = "Merlin-Orion"
78
79DEFAULT_ALTERNATE_DRIVE = True
80DEFAULT_INERTIA_ANGLE = 1. # °
81
82TAB_HARD = unicode(QtGui.QApplication.translate("merlinOrionPlugins", 'Hard'))
83LABEL_ALTERNATE_DRIVE = unicode(QtGui.QApplication.translate("merlinOrionPlugins", "Alternate drive"))
84LABEL_INERTIA_ANGLE = unicode(QtGui.QApplication.translate("merlinOrionPlugins", "Inertia angle"))
85
86ALTERNATE_DRIVE_ANGLE = 7. # °
87AXIS_ACCURACY = 0.1 # °
88AXIS_TABLE = {'yawAxis': 1,
89              'pitchAxis': 2,
90              'shutter': 1
91              }
92MANUAL_SPEED_TABLE = {'slow': 170,  # "AA0000"  / 5
93                      'alternate': 80, # "500000"
94                      'normal': 34, # "220000" nominal
95                      'fast': 17   # "110000"  * 2
96                      }
97
98
99class MerlinOrionAxis(AbstractHardwarePlugin, AbstractAxisPlugin, QtCore.QThread):
100    """
101    """
102    def __init__(self, *args, **kwargs):
103        AbstractHardwarePlugin.__init__(self, *args, **kwargs)  # Only 1?
104        AbstractAxisPlugin.__init__(self, *args, **kwargs)
105        QtCore.QThread.__init__(self)
106
107    def _init(self):
108        Logger().trace("MerlinOrionAxis._init()")
109        AbstractHardwarePlugin._init(self)
110        AbstractAxisPlugin._init(self)
111        self._hardware = MerlinOrionHardware()
112        self.__run = False
113        self.__driveFlag = False
114        self.__setPoint = None
115
116    def _defineConfig(self):
117        AbstractAxisPlugin._defineConfig(self)
118        AbstractHardwarePlugin._defineConfig(self)
119        self._addConfigKey('_alternateDrive', 'ALTERNATE_DRIVE', default=DEFAULT_ALTERNATE_DRIVE)
120        self._addConfigKey('_inertiaAngle', 'INERTIA_ANGLE', default=DEFAULT_INERTIA_ANGLE)
121
122    def activate(self):
123        Logger().trace("MerlinOrionHardware.activate()")
124        AbstractAxisPlugin.activate(self)
125
126        # Start the thread
127        self.start()
128
129    def deactivate(self):
130        Logger().trace("MerlinOrionHardware.deactivate()")
131
132        # Stop the thread
133        self._stopThread()
134        self.wait()
135        AbstractAxisPlugin.deactivate(self)
136
137    def init(self):
138        Logger().trace("MerlinOrionAxis.init()")
139        self._hardware.setAxis(AXIS_TABLE[self.capacity]),
140        AbstractHardwarePlugin.init(self)
141
142    def shutdown(self):
143        Logger().trace("MerlinOrionAxis.shutdown()")
144        self.stop()
145        AbstractHardwarePlugin.shutdown(self)
146        AbstractAxisPlugin.shutdown(self)
147
148    def run(self):
149        """ Main entry of the thread.
150        """
151        threadName = "%s_%s" % (self.name, self.capacity)
152        threading.currentThread().setName(threadName)
153        Logger().debug("MerlinOrionAxis.run(): start thread")
154        self.__run = True
155        while self.__run:
156            if self.__driveFlag:
157
158                # Choose alternate drive if needed
159                currentPos = self.read()
160                if self._config['ALTERNATE_DRIVE'] and \
161                   1.1 * self._config['INERTIA_ANGLE'] < abs(self.__setPoint - currentPos) < ALTERNATE_DRIVE_ANGLE:
162                    self._alternateDrive(self.__setPoint)
163                else:
164                    self._directDrive(self.__setPoint)
165                self.__driveFlag = False
166                self.waitEndOfDrive()  # ???
167
168            self.msleep(config.SPY_REFRESH_DELAY)
169
170        Logger().debug("MerlinOrionAxis.run(): thread terminated")
171
172    def _stopThread(self):
173        """ Stop the thread.
174        """
175        self.__run = False
176
177    def read(self):
178        pos = self._hardware.read() - self._offset
179        return pos
180
181    def drive(self, pos, useOffset=True, wait=True):
182        Logger().debug("MerlinOrionAxis.drive(): '%s' drive to %.1f" % (self.capacity, pos))
183        currentPos = self.read()
184
185        self._checkLimits(pos)
186
187        # Only move if needed
188        if abs(pos - currentPos) > AXIS_ACCURACY or not useOffset:
189            if useOffset:
190                Logger().debug("MerlinOrionAxis.drive(): offset=%.1f" % self._offset)
191                pos += self._offset
192
193            self.__setPoint = pos
194            self.__driveFlag = True # Start thread action
195
196            # Wait end of movement
197            if wait:
198                self.waitEndOfDrive()
199
200    def _directDrive(self, pos):
201        """ Default (hardware) drive.
202
203        @param pos: position to reach, in °
204        @type pos: float
205        """
206        Logger().trace("MerlinOrionAxis._directDrive()")
207        self._hardware.drive(pos)
208
209    def _alternateDrive(self, pos):
210        """ Alternate drive.
211
212        This method implements an external closed-loop regulation.
213        It is faster for angles < 6-7°, because in this case, the
214        head does not accelerate to full speed, but rather stays at
215        very low speed.
216
217        @param pos: position to reach, in °
218        @type pos: float
219        """
220        Logger().trace("MerlinOrionAxis._alternateDrive()")
221
222        # Compute initial direction
223        currentPos = self.read()
224        if pos > currentPos:
225            dir_ = '+'
226        else:
227            dir_ = '-'
228        stopRequest = False
229
230        # Alternate speed move
231        Logger().debug("MerlinOrionAxis._alternateDrive(): alternate speed move")
232        self._hardware.startJog(dir_, MANUAL_SPEED_TABLE['alternate'])
233
234        # Check when stop
235        while abs(pos - self.read()) > self._config['INERTIA_ANGLE']: # adjust inertia while moving?
236
237            # Test if a stop request has been sent
238            if not self.isMoving():
239                stopRequest = True
240                break
241            time.sleep(config.SPY_REFRESH_DELAY / 1000.)
242        self._hardware.stop()
243
244        # Final move
245        if abs(pos - self.read()) > AXIS_ACCURACY and not stopRequest:
246            Logger().debug("MerlinOrionAxis._alternateDrive(): final move")
247            self._hardware.drive(pos)
248
249    def waitEndOfDrive(self):
250        while self.isMoving():
251            time.sleep(config.SPY_REFRESH_DELAY / 1000.)
252        self.waitStop()
253
254    def startJog(self, dir_):
255        self._hardware.startJog(dir_, MANUAL_SPEED_TABLE[self._manualSpeed])
256
257    def stop(self):
258        self.__driveFlag = False
259        self._hardware.stop()
260        self.waitStop()
261
262    def waitStop(self):
263        pos = self.read()
264        time.sleep(config.SPY_REFRESH_DELAY / 1000.)
265        while True:
266            if abs(pos - self.read()) <= AXIS_ACCURACY:
267                break
268            pos = self.read()
269            time.sleep(config.SPY_REFRESH_DELAY / 1000.)
270
271    def isMoving(self):
272        status = self._hardware.getStatus()
273        if status[1] != '0' or self.__driveFlag:
274            return True
275        else:
276            return False
277
278
279class MerlinOrionAxisController(AxisPluginController, HardwarePluginController):
280    def _defineGui(self):
281        AxisPluginController._defineGui(self)
282        HardwarePluginController._defineGui(self)
283        self._addTab('Hard', TAB_HARD)
284        self._addWidget('Hard', LABEL_ALTERNATE_DRIVE, CheckBoxField, (), 'ALTERNATE_DRIVE')
285        self._addWidget('Hard', LABEL_INERTIA_ANGLE, DoubleSpinBoxField, (0.1, 9.9, 1, .1, "", u" °"), 'INERTIA_ANGLE')
286
287
288class MerlinOrionShutter(AbstractHardwarePlugin, AbstractStandardShutterPlugin):
289    def __init__(self, *args, **kwargs):
290        """
291        """
292        AbstractHardwarePlugin.__init__(self, *args, **kwargs)
293        AbstractStandardShutterPlugin.__init__(self, *args, **kwargs)
294
295    def _init(self):
296        Logger().trace("MerlinOrionShutter._init()")
297        AbstractHardwarePlugin._init(self)
298        AbstractStandardShutterPlugin._init(self)
299        self._hardware = MerlinOrionHardware()
300
301    def _defineConfig(self):
302        AbstractHardwarePlugin._defineConfig(self)
303        AbstractStandardShutterPlugin._defineConfig(self)
304
305    def _triggerOnShutter(self):
306        """ Set the shutter on.
307        """
308        self._hardware.setOutput(True)
309
310    def _triggerOffShutter(self):
311        """ Set the shutter off.
312        """
313        self._hardware.setOutput(False)
314
315    def init(self):  # Move to AbstractHardwarePlugin?
316        Logger().trace("MerlinOrionShutter.init()")
317        self._hardware.setAxis(AXIS_TABLE[self.capacity]),
318        AbstractHardwarePlugin.init(self)
319
320    def shutdown(self):
321        Logger().trace("MerlinOrionShutter.shutdown()")
322        self._triggerOffShutter()
323        AbstractHardwarePlugin.shutdown(self)
324        AbstractStandardShutterPlugin.shutdown(self)
325
326
327class MerlinOrionShutterController(StandardShutterPluginController, HardwarePluginController):
328    def _defineGui(self):
329        StandardShutterPluginController._defineGui(self)
330        HardwarePluginController._defineGui(self)
331
332
333def register():
334    """ Register plugins.
335    """
336    PluginsManager().register(MerlinOrionAxis, MerlinOrionAxisController, capacity='yawAxis', name=NAME)
337    PluginsManager().register(MerlinOrionAxis, MerlinOrionAxisController, capacity='pitchAxis', name=NAME)
338    PluginsManager().register(MerlinOrionShutter, MerlinOrionShutterController, capacity='shutter', name=NAME)
Note: See TracBrowser for help on using the repository browser.