Developing a new controller, Papywizard compatible  

Everything you need to motorize your head
User avatar
fma38
Moderator
 
Posts: 5850
Likes: 2 posts
Liked in: 2 posts
Joined: Wed Dec 07, 2005 6:21 pm
Location: Grenoble, France
Info

by fma38 » Thu Jan 28, 2010 4:34 pm

Ok, here is a first version. Just put it in your plugins dir:

linux: ~/.config/papywizard2/plugins:
windows: C:\Documents and settings\<user>\Application Data\papywizard2\plugins\

Start Papywizard, and choose 'Panoguy' plugins. I think you will be able to fix minor bugs, but feel free to contact me if needed.

Code: Select all
# -*- coding: utf-8 -*-

""" Panohead remote control.

License
=======

 - B{Papywizard} (U{http://www.papywizard.org}) is Copyright:
  - (C) 2007-2009 Frédéric Mantegazza

This software is governed by the B{CeCILL} license under French law and
abiding by the rules of distribution of free software.  You can  use,
modify and/or redistribute the software under the terms of the CeCILL
license as circulated by CEA, CNRS and INRIA at the following URL
U{http://www.cecill.info}.

As a counterpart to the access to the source code and  rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty  and the software's author,  the holder of the
economic rights,  and the successive licensors  have only  limited
liability.

In this respect, the user's attention is drawn to the risks associated
with loading,  using,  modifying and/or developing or reproducing the
software by the user in light of its specific status of free software,
that may mean  that it is complicated to manipulate,  and  that  also
therefore means  that it is reserved for developers  and  experienced
professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their
requirements in conditions enabling the security of their systems and/or
data to be ensured and,  more generally, to use and operate it in the
same conditions as regards security.

The fact that you are presently reading this means that you have had
knowledge of the CeCILL license and that you accept its terms.

Module purpose
==============

Hardware

Implements
==========

- PanoguyLowLevelHardware
- PanoguyAxis
- PanoguyAxisController
- PanoguyShutter
- PanoguyShutterController

@author: Frédéric Mantegazza
@copyright: (C) 2007-2010 Frédéric Mantegazza
@license: CeCILL
"""

__revision__ = "$Id: panoguyPlugins.py 2216 2010-01-28 12:05:01Z fma $"

import time
import sys
import threading

from PyQt4 import QtCore, QtGui

from papywizard.common import config
from papywizard.common.exception import HardwareError
from papywizard.common.loggingServices import Logger
from papywizard.plugins.pluginsManager  import PluginsManager
from papywizard.plugins.abstractAxisPlugin import AbstractAxisPlugin
from papywizard.plugins.abstractStandardShutterPlugin import AbstractStandardShutterPlugin
from papywizard.plugins.abstractHardwarePlugin import AbstractHardwarePlugin
from papywizard.plugins.axisPluginController import AxisPluginController
from papywizard.plugins.hardwarePluginController import HardwarePluginController
from papywizard.plugins.standardShutterPluginController import StandardShutterPluginController
from papywizard.view.pluginFields import ComboBoxField, LineEditField, SpinBoxField, DoubleSpinBoxField, CheckBoxField, SliderField

NAME = "Panoguy"

ENCODER_ZERO = 0x800000
AXIS_ACCURACY = 0.1  # °
AXIS_TABLE = {'yawAxis': 1,
              'pitchAxis': 2,
              'shutter': 1
              }
MANUAL_SPEED_TABLE = {'slow': 170,  # "AA0000"  / 5
                      'normal': 34, # "220000" nominal
                      'fast': 17   # "110000"  * 2
                      }


class PanoguyLowLevelHardware(QtCore.QObject):  # Inherits abstract???
    """ Low-level access to Panoguy controller.
    """
    def __init__(self,):
        """ Init PanoguyLowLevelHardware Object.
        """
        QtCore.QObject.__init__(self)

        self.__capacity = None
        self.__driver = None
        self.__encoderFullCircle = None

    def setCapacity(self, capacity):
        """
        """
        self.__capacity = capacity

    def setDriver(self, driver):
        """
        """
        self.__driver = driver

    def __decodeAxisValue(self, strValue):
        """ Decode value from axis.

        Values (position, speed...) returned by axis are
        24bits-encoded strings, high byte first.

        @param strValue: value returned by axis
        @type strValue: str

        @return: value
        @rtype: int
        """
        value = eval("0x%s" % strValue)

        return value

    def __encodeAxisValue(self, value):
        """ Encode value for axis.

        Values (position, speed...) to send to axis must be
        24bits-encoded strings, high byte first.

        @param value: value
        @type value: int

        @return: value to send to axis
        @rtype: str
        """
        strHexValue = "000000%s" % hex(value)[2:]
        strValue = strHexValue[-6:]

        return strValue.upper()

    def __encoderToAngle(self, codPos):
        """ Convert encoder value to degres.

        @param codPos: encoder position
        @type codPos: int

        @return: position, in °
        @rtype: float
        """
        return (codPos - ENCODER_ZERO) * 360. / self.__encoderFullCircle

    def __angleToEncoder(self, pos):
        """ Convert degres to encoder value.

        @param pos: position, in °
        @type pos: float

        @return: encoder position
        @rtype: int
        """
        return int(pos * self.__encoderFullCircle / 360. + ENCODER_ZERO)

    def __sendCmd(self, cmd, param=""):
        """ Send a command to the axis.

        @param cmd: command to send
        @type cmd: str

        @return: answer
        @rtype: str
        """
        cmd = "%s%d%s" % (cmd, AXIS_TABLE[self.__capacity], param)
        for nbTry in xrange(3):
            try:
                answer = ""
                self.__driver.empty()
                self.__driver.write(":%s\r" % cmd)
                c = ''
                while c not in ('=', '!'):
                    c = self.__driver.read(1)
                if c == '!':
                    c = self.__driver.read(1) # Get error code
                    raise IOError("Unknown command '%s' (err=%s)" % (cmd, c))
                answer = ""
                while True:
                    c = self.__driver.read(1)
                    if c == '\r':
                        break
                    answer += c

            except IOError:
                Logger().exception("PanoguyLowLevelHardware.__sendCmd")
                Logger().warning("PanoguyLowLevelHardware.__sendCmd(): %s axis %d can't sent command '%s'. Retrying..." % (NAME, AXIS_TABLE[self.__capacity], cmd))
            else:
                break
        else:
            raise HardwareError("%s axis %d can't send command '%s'" % (NAME, AXIS_TABLE[self.__capacity], cmd))
        #Logger().debug("PanoguyLowLevelHardware._sendCmd(): axis %d cmd=%s, ans=%s" % (AXIS_TABLE[self.__capacity], cmd, answer))

        return answer

    def initHardware(self):
        """ Init the Panoguy hardware.

        Done only once per axis.
        """
        self.__driver.acquireBus()
        try:

            # Stop motor
            self.__sendCmd("L")

            # Check motor?
            self.__sendCmd("F")

            # Get encoder full circle
            value = self.__sendCmd("a")
            self.__encoderFullCircle = self.__decodeAxisValue(value)
            Logger().debug("PanoguyLowLevelHardware.init(): full circle count=%s" % hex(self.__encoderFullCircle))

            # Get firmeware version
            value = self.__sendCmd("e")
            Logger().debug("PanoguyLowLevelHardware.init(): firmeware version=%s" % value)

        finally:
            self.__driver.releaseBus()

    def read(self):
        """ Read the axis position.

        @return: axis position, in °
        @rtype: float
        """
        self.__driver.acquireBus()
        try:
            value = self.__sendCmd("j")
        finally:
            self.__driver.releaseBus()
        pos = self.__encoderToAngle(self.__decodeAxisValue(value))
        return pos

    def drive(self, pos):
        """ Drive the axis.

        @param pos: position to reach, in °
        @type pos: float
        """
        strValue = self.__encodeAxisValue(self.__angleToEncoder(pos))
        self.__driver.acquireBus()
        try:
            self.__sendCmd("L")
            #self._sendCmd("G", "00")
            self.__sendCmd("S", strValue)
            self.__sendCmd("J")
        finally:
            self.__driver.releaseBus()

    def stop(self):
        """ Stop the axis.
        """
        self.__driver.acquireBus()
        try:
            self.__sendCmd("L")
        finally:
            self.__driver.releaseBus()

    def startJog(self, dir_, speed):
        """ Start the axis.

        @param dir_: direction ('+', '-')
        @type dir_: str

        @param speed: speed
        @type speed: int
        """
        self.__driver.acquireBus()
        try:
            self.__sendCmd("L")
            if dir_ == '+':
                self.__sendCmd("G", "0")
            elif dir_ == '-':
                self.__sendCmd("G", "1")
            else:
                raise ValueError("%s axis %d dir. must be in ('+', '-')" % (NAME, AXIS_TABLE[self.__capacity]))
            #self.__sendCmd("I", self.__encodeAxisValue(speed))
            self.__sendCmd("J")
        finally:
            self.__driver.releaseBus()

    def getStatus(self):
        """ Get axis status.

        @return: axis status
        @rtype: str
        """
        self.__driver.acquireBus()
        try:
            return self.__sendCmd("f")
        finally:
            self.__driver.releaseBus()

    def setOutput(self, state):
        """ Set output state.

        @param state: new state of the the output
        @type state: bool
        """
        self.__driver.acquireBus()
        try:
            self.__sendCmd("O", str(int(state)))
        finally:
            self.__driver.releaseBus()


class PanoguyAxis(AbstractHardwarePlugin, AbstractAxisPlugin):
    def __init__(self, *args, **kwargs):
        AbstractHardwarePlugin.__init__(self, *args, **kwargs)
        AbstractAxisPlugin.__init__(self, *args, **kwargs)

    def _init(self):
        Logger().trace("PanoguyAxis._init()")
        AbstractHardwarePlugin._init(self)
        AbstractAxisPlugin._init(self)
        self.__lowLevelHardware = PanoguyLowLevelHardware()  # Move to parent class?

    def _defineConfig(self):
        AbstractHardwarePlugin._defineConfig(self)
        AbstractAxisPlugin._defineConfig(self)

    def init(self):
        Logger().trace("PanoguyAxis.init()")
        AbstractAxisPlugin.init(self)
        self.__lowLevelHardware.setCapacity(self.capacity),
        self.__lowLevelHardware.setDriver(self._driver)
        self.__lowLevelHardware.initHardware()

    def shutdown(self):
        Logger().trace("PanoguyAxis.shutdown()")
        self.stop()
        AbstractAxisPlugin.shutdown(self)

    def read(self):
        pos = self.__lowLevelHardware.read() - self._offset
        return pos

    def drive(self, pos, useOffset=True, wait=True):
        Logger().debug("PanoguyAxis.drive(): '%s' drive to %.1f" % (self.capacity, pos))
        currentPos = self.read()

        self._checkLimits(pos)

        if useOffset:
            pos += self._offset

        # Only move if needed
        if abs(pos - currentPos) > AXIS_ACCURACY:
            self.__lowLevelHardware.drive(pos)

            # Wait end of movement
            if wait:
                self.waitEndOfDrive()

    def waitEndOfDrive(self):
        while self.isMoving():
            time.sleep(0.1)
        self.waitStop()

    def startJog(self, dir_):
        self.__lowLevelHardware.startJog(dir_, MANUAL_SPEED_TABLE[self._manualSpeed])

    def stop(self):
        self.__driveFlag = False
        self.__lowLevelHardware.stop()
        self.waitStop()

    def waitStop(self):
        pos = self.read()
        time.sleep(0.05)
        while True:
            if abs(pos - self.read()) <= AXIS_ACCURACY:
                break
            pos = self.read()
            time.sleep(0.05)

    def isMoving(self):
        status = self.__lowLevelHardware.getStatus()
        if status != '0':
            return True
        else:
            return False


class PanoguyAxisController(AxisPluginController, HardwarePluginController):
    def _defineGui(self):
        AxisPluginController._defineGui(self)
        HardwarePluginController._defineGui(self)


class PanoguyShutter(AbstractHardwarePlugin, AbstractStandardShutterPlugin):
    def __init__(self, *args, **kwargs):
        """
        """
        AbstractHardwarePlugin.__init__(self, *args, **kwargs)
        AbstractStandardShutterPlugin.__init__(self, *args, **kwargs)

    def _init(self):
        Logger().trace("PanoguyShutter._init()")
        AbstractHardwarePlugin._init(self)
        AbstractStandardShutterPlugin._init(self)
        self.__lowLevelHardware = PanoguyLowLevelHardware()  # Move to parent class?

    def _defineConfig(self):
        AbstractHardwarePlugin._defineConfig(self)
        AbstractStandardShutterPlugin._defineConfig(self)

    def _triggerOnShutter(self):
        """ Set the shutter on.
        """
        self.__lowLevelHardware.setOutput(True)

    def _triggerOffShutter(self):
        """ Set the shutter off.
        """
        self.__lowLevelHardware.setOutput(False)

    def init(self):
        Logger().trace("PanoguyShutter.init()")
        AbstractHardwarePlugin.init(self)
        AbstractStandardShutterPlugin.init(self)
        self.__lowLevelHardware.setCapacity(self.capacity),
        self.__lowLevelHardware.setDriver(self._driver)
        self.__lowLevelHardware.initHardware()

    def shutdown(self):
        Logger().trace("PanoguyShutter.shutdown()")
        self._triggerOffShutter()
        AbstractHardwarePlugin.shutdown(self)
        AbstractStandardShutterPlugin.shutdown(self)


class PanoguyShutterController(StandardShutterPluginController, HardwarePluginController):
    def _defineGui(self):
        StandardShutterPluginController._defineGui(self)
        HardwarePluginController._defineGui(self)


def register():
    """ Register plugins.
    """
    PluginsManager().register(PanoguyAxis, PanoguyAxisController, capacity='yawAxis', name=NAME)
    PluginsManager().register(PanoguyAxis, PanoguyAxisController, capacity='pitchAxis', name=NAME)
    PluginsManager().register(PanoguyShutter, PanoguyShutterController, capacity='shutter', name=NAME)
Frédéric

User avatar
panoguy
Member
 
Topic author
Posts: 106
Likes: 1 post
Liked in: 0 post
Joined: Mon Jan 25, 2010 3:55 am
Location: Bavaria
Info

by panoguy » Fri Jan 29, 2010 1:20 am

Hey Frederic,
this is unbelievable, you made it in this short time frame!
Thank you very much!

I got the plugin installed and it works so far.
The connection is established and the head is moving to the right positions, only the J command is not in synch.
IOError: Timeout while reading on serial bus (size=1, data='')
2010-01-29 01:15:21,223::Shooting::WARNING::PanoguyLowLevelHardware.__sendCmd(): Panoguy axis 1 can't sent command 'J1'. Retrying...
I will evaluate tomorrow this issue and also review the plugin.

User avatar
fma38
Moderator
 
Posts: 5850
Likes: 2 posts
Liked in: 2 posts
Joined: Wed Dec 07, 2005 6:21 pm
Location: Grenoble, France
Info

by fma38 » Fri Jan 29, 2010 7:51 am

:)

You can add some debug informations in the __sendCmd() method, by adding:

Code: Select all
Logger().debug("PanoguyLowLevelHardware.__sendCmd(): c=%s" % c)

after each line:

Code: Select all
c = self.__driver.read(1)
Frédéric

User avatar
fma38
Moderator
 
Posts: 5850
Likes: 2 posts
Liked in: 2 posts
Joined: Wed Dec 07, 2005 6:21 pm
Location: Grenoble, France
Info

by fma38 » Fri Jan 29, 2010 10:58 am

Reading again your message, there is something I don't understand: the 'J' command is sent for both 'goto' and 'start moving'. What I understood is that you where able to drive the head with the 'goto' function. Is that right? So, it is the entire 'start moving' function which fails; your routine may block once it received the 'G' command, and does not answer to the 'J' one...

BTW, don't your think your could remove the echo mode when Papywizard send a command? Are your using different lines for read and write signals on your serial port?
Frédéric

User avatar
panoguy
Member
 
Topic author
Posts: 106
Likes: 1 post
Liked in: 0 post
Joined: Mon Jan 25, 2010 3:55 am
Location: Bavaria
Info

by panoguy » Fri Jan 29, 2010 11:56 am

Hi,
till now I did not implement the start moving function only the goto.
I started to test the movement with a preset template 6x2 pictures and the head moves both axis to the right positions. In the GUI it shows the dots as red and not as green, and in the log file the error I posted.
As I'm in the office now I could not run further testing, but will do again during the weekend.
I think the problem is the following;
Right after receiving the S:<position> command, I acknowledge the command and start driving the motor.
As the motor is running I don't respond to the J command and therefore it run's into this timeout.
After the motor is stopped the head can respond again and send the actual position and continue to receive the next goto command.
I wonder if the J command is needed at at. Do you know the exact purpose of it on the Merlin?

I do not send back any echo and use the normal RX/TX line on Com1 port of the AVR board

User avatar
panoguy
Member
 
Topic author
Posts: 106
Likes: 1 post
Liked in: 0 post
Joined: Mon Jan 25, 2010 3:55 am
Location: Bavaria
Info

by panoguy » Fri Jan 29, 2010 11:42 pm

Hi Frederic
I have implemented the following workaround for now by changing the plugin:
1. after sending the goto position, sleep for 1 sec. Anyway the head needs some time to drive to the new position
2. and I have removed the J command
it is not a very elegant solution but at least it works. Do you have a better idea how to solve it? As said the issue is caused by the controller not being able to respond to any request during the time the motors are on.

def drive(self, pos):
""" Drive the axis.

@param pos: position to reach, in °
@type pos: float
"""
strValue = self.__encodeAxisValue(self.__angleToEncoder(pos))
self.__driver.acquireBus()
try:
self.__sendCmd("L")
#self._sendCmd("G", "00")
self.__sendCmd("S", strValue)
time.sleep(1)
#self.__sendCmd("J")
finally:
self.__driver.releaseBus()

User avatar
fma38
Moderator
 
Posts: 5850
Likes: 2 posts
Liked in: 2 posts
Joined: Wed Dec 07, 2005 6:21 pm
Location: Grenoble, France
Info

by fma38 » Sat Jan 30, 2010 9:38 am

I though I have posted an answer, yesterday, but I may have forgot to validate the message after the preview!

I was saying that the best way is like you did here, execute the command right after 'S' (for goto), or 'G' (for start moving).

I understand your sleep(), here, but what about the refresh position? As it occurs in a thread, it should fail (look in the logs), and the position is not refreshed during that second. Am I right?
Frédéric

User avatar
panoguy
Member
 
Topic author
Posts: 106
Likes: 1 post
Liked in: 0 post
Joined: Mon Jan 25, 2010 3:55 am
Location: Bavaria
Info

by panoguy » Sat Jan 30, 2010 6:08 pm

Yes you are right the position is not refreshed during this second because my controller is busy and can't answer the request.
Since I add the delay it works fine even the position is not refreshed during that time. Anyway it is not needed to know the position every single degree.
As I mentioned earlier already, it would be much simpler if papywizard would not wait for the slave to acknowledge the command.
If the command is not acknowledged, papywizard should keep repeating the same command for a while until it receives the answer from the slave. Or have a parameter in the plugin to define the number of retries.

I also succeeded today to successfully connect the device via BT.
I realized I always have to select Com port -1 in papywizard to get the connection established to the right port number. Ie. I have virtual port number 7 (Windows 7 64 bit) and I need to use com port 6 in papywizard.
Is this a known bug?

Now I have a question on the move command.
How does this work with the Merlin and papywizard when you press one of the 4 move buttons?
Does the head move as long you press one of the 4 buttons?

no avatar
mediavets
Moderator
 
Posts: 16415
Likes: 2 posts
Liked in: 130 posts
Joined: Wed Nov 14, 2007 2:12 pm
Location: Isleham, Cambridgeshire, UK.
Info

by mediavets » Sat Jan 30, 2010 6:17 pm

panoguy wrote:I realized I always have to select Com port -1 in papywizard to get the connection established to the right port number. Ie. I have virtual port number 7 (Windows 7 64 bit) and I need to use com port 6 in papywizard.
Is this a known bug?

Let's call it an 'undocumented feature' - Frederic doesn't consider it a bug because in his view ports should be enumerated from zero. So you can enter either '0' or 'COM1' (in your case '6' or 'COM7')in the dialogue. But it is a little counter intuitive for Windows users.
Andrew Stephens
Many different Nodal Ninja and Agnos pano heads. Merlin/Panogear mount with Papywizard on Nokia Internet tablets.
Nikon D5100 and D40, Sigma 8mm f3.5 FE, Nikon 10.5mm FE, 35mm, 50mm, 18-55mm, 70-210mm. Promote control.

User avatar
fma38
Moderator
 
Posts: 5850
Likes: 2 posts
Liked in: 2 posts
Joined: Wed Dec 07, 2005 6:21 pm
Location: Grenoble, France
Info

by fma38 » Sat Jan 30, 2010 7:42 pm

Panoguy, I already repeat the command if the timeout occurs; I do that 3 times, then stop. And yes, it is possible to add a param in the config. dialog... But on some OS/drivers (Windows/bluetooth, the most used, in fact!) the timeout can't be changed. So, if there is really a problem, and you set the retry to a high value, you may wait a long time before it finally answer; the application will seem to be freezed.

But what I don't understand is why you don't first acknowledge the command before starting to execute it? But also keep in mind that in such application, there are commands which can't be delayed, like a stop. This one should be executed immediatly, as it can be an emmergency from the user ! But it is not a problem, there, if it is not immediatly acknowledge.

Last, to move the head with the keys, it work like this: when you press the key, I send a 'start moving' command, and when you release the key, I send a 'stop' command. So, in your firmeware, once the axis is started, you just have to wait for a stop. But a'read' command should be accepted while moving. Other comands don't have to be accepted (it is up to you, but such feature is not used in Papywizard). Just return an error code ('!<err>') in this case.

And about the come port, as it is explained in the documentation¹, you can either enter a number or the name of the port (COM7 for Windows, or /dev/ttyxxx for Gnu/linux or MacOS). *This* is intuitive ;)

¹ http://www.papywizard.org/wiki/UserGuideSvn#Drivertab
Frédéric

no avatar
mediavets
Moderator
 
Posts: 16415
Likes: 2 posts
Liked in: 130 posts
Joined: Wed Nov 14, 2007 2:12 pm
Location: Isleham, Cambridgeshire, UK.
Info

by mediavets » Sat Jan 30, 2010 7:51 pm

fma38 wrote:And about the com port, as it is explained in the documentation¹, you can either enter a number or the name of the port (COM7 for Windows, or /dev/ttyxxx for Gnu/linux or MacOS). *This* is intuitive ;)

¹ http://www.papywizard.org/wiki/UserGuideSvn#Drivertab

My apologies - I should have checked. I suspect it may not have been documented when it caused me a problem way back.
Last edited by mediavets on Sat Jan 30, 2010 7:52 pm, edited 1 time in total.
Andrew Stephens
Many different Nodal Ninja and Agnos pano heads. Merlin/Panogear mount with Papywizard on Nokia Internet tablets.
Nikon D5100 and D40, Sigma 8mm f3.5 FE, Nikon 10.5mm FE, 35mm, 50mm, 18-55mm, 70-210mm. Promote control.

User avatar
panoguy
Member
 
Topic author
Posts: 106
Likes: 1 post
Liked in: 0 post
Joined: Mon Jan 25, 2010 3:55 am
Location: Bavaria
Info

by panoguy » Sat Jan 30, 2010 9:05 pm

mediavets wrote:
fma38 wrote:And about the com port, as it is explained in the documentation¹, you can either enter a number or the name of the port (COM7 for Windows, or /dev/ttyxxx for Gnu/linux or MacOS). *This* is intuitive ;)

¹ http://www.papywizard.org/wiki/UserGuideSvn#Drivertab

My apologies - I should have checked. I suspect it may not have been documented when it caused me a problem way back.

Ahh yes, who of us ever reads the documentation ... I have to omit I don't ;-)
Thank you for the hint, as long it works as designed, it's not a problem.

User avatar
panoguy
Member
 
Topic author
Posts: 106
Likes: 1 post
Liked in: 0 post
Joined: Mon Jan 25, 2010 3:55 am
Location: Bavaria
Info

by panoguy » Sat Jan 30, 2010 9:36 pm

fma38 wrote:I already repeat the command if the timeout occurs; I do that 3 times,

Would it be possible to repeat the command let's say 10 times? or have a parameter how often it should be repeated?

fma38 wrote:But what I don't understand is why you don't first acknowledge the command before starting to execute it? But also keep in mind that in such application, there are commands which can't be delayed, like a stop. This one should be executed immediatly, as it can be an emmergency from the user ! But it is not a problem, there, if it is not immediatly acknowledge.

There seems we have a misunderstanding here. Of course I do acknowledge the S:<position> command before executing it. This is not the issue since I removed the J command the next timeout comes from requesting the position:
can't sent command 'j1'. Retrying... or j2,
So my workaround delays this request and the program runs fine so far.

I agree that a stop command should always be executed and I can catch this exception.
The receive function is still executed in the interrupt routine and it receives all commands, but as long the motors are running all commands are ignored, except the stop command.

fma38 wrote:Last, to move the head with the keys, it work like this: when you press the key, I send a 'start moving' command, and when you release the key, I send a 'stop' command. So, in your firmeware, once the axis is started, you just have to wait for a stop. But a'read' command should be accepted while moving. Other comands don't have to be accepted (it is up to you, but such feature is not used in Papywizard). Just return an error code ('!<err>') in this case.

ok, this is clear, I should be able to handle it now.

User avatar
fma38
Moderator
 
Posts: 5850
Likes: 2 posts
Liked in: 2 posts
Joined: Wed Dec 07, 2005 6:21 pm
Location: Grenoble, France
Info

by fma38 » Sat Jan 30, 2010 10:39 pm

I understand what is the timeout problem: when I request a position from the core, I send both axis positions, and the position is first sent to the yaw axis, then to the pitch axis. And while you start the first, the second can't be executed, and the timeout occurs... In Merlin, there are 2 processors, running in //...
Frédéric

User avatar
panoguy
Member
 
Topic author
Posts: 106
Likes: 1 post
Liked in: 0 post
Joined: Mon Jan 25, 2010 3:55 am
Location: Bavaria
Info

by panoguy » Sun Jan 31, 2010 11:19 pm

fma38 wrote:I understand what is the timeout problem: when I request a position from the core, I send both axis positions, and the position is first sent to the yaw axis, then to the pitch axis. And while you start the first, the second can't be executed, and the timeout occurs... In Merlin, there are 2 processors, running in //...

Yes this seems to be the problem. Is there a way to change this behavior in the plugin?
And also for future version it would be helpful to define the number of command retries in the plugin. In this way you could also workaround the timeout issue.

no avatar
hankkarl
Member
 
Posts: 1284
Likes: 0 post
Liked in: 0 post
Joined: Tue Feb 21, 2006 5:32 pm
Location: Connecticut, USA
Info

by hankkarl » Mon Feb 01, 2010 1:30 am

claudevh wrote:
The Merlin/Orion may have a new program

What do-you mean by that ?

http://www.skywatcherusa.com/multifunction-mount-s21300.html describes a version of the Merlin that has a pair of rails that let you adjust the camera up and down, and left and right. All you need is a fore-and-aft rail (like a macro rail).

The following paragraphs describe the new S/W (or perhaps its old S/W that I don't know about?). Note that the last sentence implies some sort of cable to a camera, but also mentions six stopping points.

Quote:
TERRESTRIAL MODE
MultiMount includes a multi-location terrestrial surveillance mode. The sophisticated built-in software allows land surveys for 6 different locations. No set-up is required to operate in land mode. Astronomical tracking is turned off for terrestrial survey applications.

Given the mount is kept in, or returned to the same exact physical orientation from session to session, the software memorizes the positions the telescope is to point to and automatically goes to these positions on demand. You simply press the terrestrial "cruise" button and the number of the location desired, the telescope then moves into viewing position. It is also possible to command the telescope to cruise to each location in sequence, stop for a moment, and move on to the next location. You can scout out your favorite points of interest across the urban panorama automatically. Enjoy quick access to what is happening at your favorite bird feeder. Keep an eye on property access points, or use the technology for thousands of other interesting applications. The mount accommodates most spotting scopes, cameras, video camcorders, and binoculars with 1/4", 20-thread receptors. A Snap cable is provided to allow automatic imaging at each of the six stopping points while in sequential "Cruise & Shoot" mode using a compatible digital camera.
Last edited by hankkarl on Mon Feb 01, 2010 1:31 am, edited 1 time in total.

User avatar
claudevh
Moderator
 
Posts: 1349
Likes: 0 post
Liked in: 0 post
Joined: Sun Nov 25, 2007 11:12 pm
Location: Mont-Saint-André (Belgium)
Info

by claudevh » Mon Feb 01, 2010 2:04 am

hankkarl, thank-you for your informations ! :)

Those are not really big changes ... but more "marketing" changes ;)
Last edited by claudevh on Mon Feb 01, 2010 2:04 am, edited 1 time in total.
:cool: Claude :cool:
Merlin + Papywizard on Windows 7 & Nokia 770 § N810 & Acer (Netbook) + PanoramaApp Androïd + Deltawave PapyMerlin BT + Autopano
Spherical Pano (180 x 360) with Canon 40D + Canon EF-S 10-22mm f/3.5-4.5 Zoom & Pôle Pano with Canon 5D MK2 and shaved Tokina 10-17 3.5-4.5 AF DX Fisheye
Gigapixel photography with Nikon D200 + Sigma 70-200 F 2.8 EX DG APO HSM

User avatar
fma38
Moderator
 
Posts: 5850
Likes: 2 posts
Liked in: 2 posts
Joined: Wed Dec 07, 2005 6:21 pm
Location: Grenoble, France
Info

by fma38 » Mon Feb 01, 2010 8:06 am

panoguy wrote:
fma38 wrote:I understand what is the timeout problem: when I request a position from the core, I send both axis positions, and the position is first sent to the yaw axis, then to the pitch axis. And while you start the first, the second can't be executed, and the timeout occurs... In Merlin, there are 2 processors, running in //...

Yes this seems to be the problem. Is there a way to change this behavior in the plugin?

Nop, as it is not a plugin behaviour, but a more global design...

And also for future version it would be helpful to define the number of command retries in the plugin. In this way you could also workaround the timeout issue.

I will add these 2 params in the general plugin dialog, in the driver tab...
Frédéric

no avatar
Greg Nuspel
Member
 
Posts: 169
Likes: 0 post
Liked in: 0 post
Joined: Tue Oct 06, 2009 6:50 pm
Location: Calgary, Alberta, Canada
Info

by Greg Nuspel » Mon Feb 01, 2010 3:06 pm

I was wondering if you could just upload the sequence of shots to the controller in one upload. Once the mount starts to shoot it takes care of everything only looking for emergency stop command if it comes. This way if communication drops out the mount knows what to do. I just don't know if there is enough memory in the controller to do this. It can still send progress reports to the user interface device. This also facilitates placing the camera in a place where communication is not possible and you use a start delay time. Just another idea that maybe totally unobtainable.
--Greg Nuspel

User avatar
fma38
Moderator
 
Posts: 5850
Likes: 2 posts
Liked in: 2 posts
Joined: Wed Dec 07, 2005 6:21 pm
Location: Grenoble, France
Info

by fma38 » Mon Feb 01, 2010 3:26 pm

I can't do that without (big) modifications to refresh the GUI while you send progress...

Think that you will also have to take care of all timings (stab. delay, shooting time). And it can take a lot of memory to store all commands.
Frédéric

User avatar
panoguy
Member
 
Topic author
Posts: 106
Likes: 1 post
Liked in: 0 post
Joined: Mon Jan 25, 2010 3:55 am
Location: Bavaria
Info

by panoguy » Mon Feb 01, 2010 8:28 pm

fma38 wrote:I can't do that without (big) modifications to refresh the GUI while you send progress...

Think that you will also have to take care of all timings (stab. delay, shooting time). And it can take a lot of memory to store all commands.

Yes I agree, the communication like it is today is fine, each single command needs to be confirmed and executed by the client. In case commands are not confirmed by the client, the server should repeat the command until it is confirmed and not run into exception after 3 unconfirmed commands.
It should not be a problem anymore if we can configure how often the commands gets repeated by PPW.

User avatar
fma38
Moderator
 
Posts: 5850
Likes: 2 posts
Liked in: 2 posts
Joined: Wed Dec 07, 2005 6:21 pm
Location: Grenoble, France
Info

by fma38 » Mon Feb 01, 2010 8:38 pm

I added the timeout and retry params in the plugin dialog... I will release a new version soon.
Frédéric

User avatar
panoguy
Member
 
Topic author
Posts: 106
Likes: 1 post
Liked in: 0 post
Joined: Mon Jan 25, 2010 3:55 am
Location: Bavaria
Info

by panoguy » Tue Feb 02, 2010 8:18 am

fma38 wrote:I understand what is the timeout problem: when I request a position from the core, I send both axis positions, and the position is first sent to the yaw axis, then to the pitch axis. And while you start the first, the second can't be executed, and the timeout occurs... In Merlin, there are 2 processors, running in //...

Wanted to come back on this topic and ask you to consider the following:
Every single instruction executed on the AVR consumes resources and battery power, therefore any unnecessary function should be avoided.
For example, you could use the AVR to check his own battery voltage. A typical program would do this in a loop and execute the check as often as possible. In this mode the AVR will drain a few 20-40mA from your battery. Depending on the capacity, the battery will be empty after a few days. You could achieve the same check by sending he AVR into standby (in this mode it will consume only a few uA) and wake it up every few seconds to execute the battery check. The AVR will execute the few instructions needed to check the battery voltage and go back to sleep. In this way your battery live will be extended for months.

Here you can get an idea what I mean: HDR-Jack - The ultra-compact interval and HDR trigger
http://www.doc-diy.net/photo/hdr-jack/

Even if Merlin has 2 axes, it is not needed to request the position of the axis which was not moved.
Also when using stepper motors without a encoder you have to trust that the goto position you requested will be equal the head position your request after the motor is stopped. This does also mean you don't have to request the motor position at all, as long the motor is moving. So this could be simplified by asking the position only before the motor starts moving and after the motor stops. Of course you would ask the status to know if the head is still moving.
In case of manual positioning (not the goto function) this is different as you want to show the head position is real time on the GUI... But again only the position of the moving motor needs to be updated.

Please consider if you can remove some requests which are not needed...

User avatar
panoguy
Member
 
Topic author
Posts: 106
Likes: 1 post
Liked in: 0 post
Joined: Mon Jan 25, 2010 3:55 am
Location: Bavaria
Info

by panoguy » Tue Feb 02, 2010 8:23 am

I have an additional question related to the gear backlash.
Do you take care of it or is the slave responsible to compensate the backlash when the motor direction is toggled?

I have seen there is a parameter in the plugin,
AXIS_ACCURACY = 0.1 # °
what is this good for?

User avatar
fma38
Moderator
 
Posts: 5850
Likes: 2 posts
Liked in: 2 posts
Joined: Wed Dec 07, 2005 6:21 pm
Location: Grenoble, France
Info

by fma38 » Tue Feb 02, 2010 11:28 am

I understand your energy-saving problem. I think it is possible to improve the plugin behaviour regarding this problem...

The AXIS_ACCURACY is internally used to avoid a positionning request if the position is OK. So it is good for the point #1.

And no, I don't manage the backlash. Be aware that it is not that simple, and mainly depends on the load. For example, on the pitch axis, you don't know if the load will put the backlash on the up direction, or on the down direction, depending how the camera/lens is balanced... The wind can also be a nice trap for the yaw axis ;)
Frédéric

PreviousNext

Who is online

Users browsing this forum: No registered users and 1 guest