olihar wrote:Here is the motion curve from it made in Nuke.
You already have all the low-level parts (just use the code from Papywizard; I can provide a version without any dependencies to Papywizard)
# -*- coding: utf-8 -*-
""" Panohead remote control.
License
=======
- B{Papywizard} (U{http://www.papywizard.org}) is Copyright:
- (C) 2007-2010 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
==========
- HardwareError
- BluetoothDriver
- MerlinOrionHardware
@author: Frédéric Mantegazza
@copyright: (C) 2007-2010 Frédéric Mantegazza
@license: CeCILL
"""
__revision__ = "0.2"
import time
import bluetooth
ENCODER_ZERO = 0x800000
BT_ADDRESS = "00:50:C2:58:56:6B"
TIMEOUT = 1.
CONNECT_DELAY = 8.
YAW_AXIS = 1
PITCH_AXIS = 2
SHUTTER_AXIS = 1
class HardwareError(Exception):
pass
class BluetoothDriver(object):
""" Driver for bluetooth connection.
"""
def __init__(self, address=BT_ADDRESS):
""" Init the bluetooth driver object.
"""
super(BluetoothDriver, self).__init__()
print "BluetoothDriver._init(): trying to connect to %s..." % address
try:
self.__sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
self.__sock.connect((address, 1))
try:
self.__sock.settimeout(TIMEOUT)
except NotImplementedError:
print "BluetoothDriver._init(): bluetooth stack does not implment settimeout()"
except bluetooth.BluetoothError, error:
err, msg = eval(error.message)
raise HardwareError(msg)
else:
time.sleep(CONNECT_DELAY)
print "BluetoothDriver._init(): successfully connected to %s" % address
def shutdown(self):
self.__sock.close()
def write(self, data):
#print "BluetoothDriver.write(): data=%s" % repr(data)
size = self.__sock.send(data)
return size
def read(self, size):
data = self.__sock.recv(size)
#print "BluetoothDriver.read(): data=%s" % repr(data)
if size and not data:
raise IOError("Timeout while reading on bluetooth bus (size=%d, data=%s)" % (size, repr(data)))
else:
return data
class MerlinOrionHardware(object):
""" Merlin/Orion low-level hardware.
"""
def __init__(self, axis, driver):
""" Init the MerlinOrionHardware object.
"""
super(MerlinOrionHardware, self).__init__()
self.__axis = axis
self.__driver = driver
self.__encoderFullCircle = None
def __decodeAxisValue(self, strValue):
""" Decode value from axis.
Values (position, speed...) returned by axis are
32bits-encoded strings, low byte first.
@param strValue: value returned by axis
@type strValue: str
@return: value
@rtype: int
"""
value = 0
for i in xrange(3):
value += eval("0x%s" % strValue[i*2:i*2+2]) * 2 ** (i * 8)
return value
def __encodeAxisValue(self, value):
""" Encode value for axis.
Values (position, speed...) to send to axis must be
32bits-encoded strings, low byte first.
@param value: value
@type value: int
@return: value to send to axis
@rtype: str
"""
strHexValue = "000000%s" % hex(value)[2:]
strValue = strHexValue[-2:] + strHexValue[-4:-2] + strHexValue[-6:-4]
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, self.__axis, param)
#print "MerlinOrionHardware.__sendCmd(): axis %d cmd=%s" % (self.__axis, repr(cmd))
for nbTry in range(3):
try:
answer = ""
self.__driver.write(":%s\r" % cmd)
c = ''
while c not in ('=', '!'):
c = self.__driver.read(1)
#Lprint "MerlinOrionHardware.__sendCmd(): c=%s" % repr(c)
if c == '!':
c = self.__driver.read(1) # Get error code
raise IOError("Error in command %s (err=%s)" % (repr(cmd), c))
answer = ""
while True:
c = self.__driver.read(1)
#print "MerlinOrionHardware.__sendCmd(): c=%s" % repr(c)
if c == '\r':
break
answer += c
except IOError:
print "MerlinOrionHardware.__sendCmd(): axis %d can't sent command %s. Retrying..." % (self.__axis, repr(cmd))
else:
break
else:
raise HardwareError("axis %d can't send command %s" % (self.__axis, repr(cmd)))
#print "MerlinOrionHardware.__sendCmd(): axis %d ans=%s" % (self.__axis, repr(answer))
return answer
def init(self):
""" Init the MerlinOrion hardware.
Done only once per axis.
"""
# Stop motor
self.__sendCmd("L")
# Check motor?
self.__sendCmd("F")
# Get firmeware version
value = self.__sendCmd("e")
print "MerlinOrionHardware.init(): firmeware version=%s" % value
# Get encoder full circle
value = self.__sendCmd("a")
self.__encoderFullCircle = self.__decodeAxisValue(value)
print "MerlinOrionHardware.init(): encoder full circle=%s" % hex(self.__encoderFullCircle)
# Get sidereal rate
value = self.__sendCmd("D")
print "MerlinOrionHardware.init(): sidereal rate=%s" % hex(self.__decodeAxisValue(value))
def read(self):
""" Read the axis position.
@return: axis position, in °
@rtype: float
"""
value = self.__sendCmd("j")
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.__sendCmd("L")
self.__sendCmd("G", "00")
self.__sendCmd("S", strValue)
self.__sendCmd("J")
def stop(self):
""" Stop the axis.
"""
self.__sendCmd("L")
def startJog(self, dir_, speed):
""" Start the axis.
@param dir_: direction ('+', '-')
@type dir_: str
@param speed: speed
@type speed: int
"""
self.__sendCmd("L")
if dir_ == '+':
self.__sendCmd("G", "30")
elif dir_ == '-':
self.__sendCmd("G", "31")
else:
raise ValueError("Axis %d dir. must be in ('+', '-')" % self.__axis)
self.__sendCmd("I", self.__encodeAxisValue(speed))
self.__sendCmd("J")
def getStatus(self):
""" Get axis status.
@return: axis status
@rtype: str
"""
return self.__sendCmd("f")
def setOutput(self, state):
""" Set output state.
@param state: new state of the the output
@type state: bool
"""
self.__sendCmd("O", str(int(state)))
def test():
""" Test.
"""
driver = BluetoothDriver("00:50:C2:58:56:6B")
yaw = MerlinOrionHardware(YAW_AXIS, driver)
pitch = MerlinOrionHardware(PITCH_AXIS, driver)
shutter = MerlinOrionHardware(SHUTTER_AXIS, driver)
yaw.init()
pitch.init()
yaw.drive(10.)
pitch.drive(-5.)
while yaw.getStatus()[1] != '0':
time.sleep(0.1)
while pitch.getStatus()[1] != '0':
time.sleep(0.1)
shutter.setOutput(1)
time.sleep(0.5)
shutter.setOutput(0)
driver.shutdown()
if __name__ == "__main__":
""" test() won't be executed if the module is imported.
"""
test()
# -*- coding: utf-8 -*-
""" Panohead remote control.
License
=======
- B{Papywizard} (U{http://www.papywizard.org}) is Copyright:
- (C) 2007-2010 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
==========
- HardwareError
- BluetoothDriver
- MerlinOrionHardware
@author: Frédéric Mantegazza
@author: Jones Henry Subbiah
@copyright: (C) 2007-2010 Frédéric Mantegazza
@license: CeCILL
"""
__revision__ = "$Id: merlinOrionHardware.py 2280 2010-02-13 12:27:30Z fma $"
import time
import serial
import math
#import bluetooth
ENCODER_ZERO = 0x800000
BT_ADDRESS = "00:50:C2:58:56:6B"
TIMEOUT = 1.
CONNECT_DELAY = 8.
YAW_AXIS = 1
PITCH_AXIS = 2
SHUTTER_AXIS = 1
SERIAL_PORT = "COM4"
SERIAL_BAUDRATE = 9600
class Point:
""" Point represent the Bezier control points
x represents yaw and y represents pitch
"""
def __init__(self, x, y):
self.x, self.y = float(x), float(y)
def pprint(self):
print "(%0.4f, %0.4f)" % (self.x, self.y)
class BezierCurve(object):
""" class to generate Bezier curve
"""
def __init__(self, points):
self._points, self._steps = points, 30
#
# Gets input points for a Bezier curve
# Returns a list of points equals the number of steps the
# head stops and takes photos.
#
def bezier(self):
outlist = []
npoints = len(self._points)
for j in range(self._steps):
#print "j: %d" % j
# the increment is always between 0.0 to 1.0
inc = 1.0 * j / self._steps
x, y = 0.0, 0.0
# for each Bezier control points
for i in range(npoints):
poly = self.bpoly(npoints-1, i, inc)
#print "i: %d, inc: %.04f, poly: %.04f" % (i, inc, poly)
x += self._points[i].x * poly
y += self._points[i].y * poly
#print "x: %.04f, y: %.04f" % (x,y)
outlist.append(Point(x,y))
# add the last control point. So a step of 300 will result 301 shots
# not neccessary - just for completeness
outlist.append(Point(self._points[npoints-1].x,self._points[npoints-1].y))
return outlist
# this function calculate optimum number of steps to travel the curve
# The fov is equal to horizontal FOV of lens used divided by 210.
# 210 is derived as 7 secs of 30 fps video.
# update the FOV field. The current one is for 150mm lens on a 1.6 crop camera
# divided by 210
# It starts at 30 steps, finds the curve points, finds the difference of the first
# 2 points. If it is less than the FOV/210, then that is the minimal steps needed.
def findsteps(self):
fov = 0.0410
steps = 0
l = []
while True:
steps += 30
self._steps = steps
l = self.bezier()
print "steps: %d, l[1].x: %f, l[0].x: %f, diff: %f" % (steps, l[1].x, l[0].x, math.fabs(l[1].x - l[0].x))
if math.fabs(l[1].x - l[0].x) < fov:
break
print "steps: %d" % steps
# finds the polynomical for a given position
def bpoly(self, order, position, inc):
""" Bezier polynomial term
"""
return self.bcoeff(order, position) * math.pow(inc, position) * math.pow ( 1.0 - inc, (order - position ) )
# find the coefficient for a given position
def bcoeff(self, order, position):
""" Bezier coefficient
"""
return math.factorial( order ) / ( math.factorial( position ) * math.factorial( order - position ) )
class HardwareError(Exception):
pass
class BluetoothDriver(object):
""" Driver for bluetooth connection.
"""
def __init__(self, address=BT_ADDRESS):
""" Init the bluetooth driver object.
"""
super(BluetoothDriver, self).__init__()
print "BluetoothDriver._init(): trying to connect to %s..." % address
try:
self.__sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
self.__sock.connect((address, 1))
try:
self.__sock.settimeout(TIMEOUT)
except NotImplementedError:
print "BluetoothDriver._init(): bluetooth stack does not implment settimeout()"
except bluetooth.BluetoothError, error:
err, msg = eval(error.message)
raise HardwareError(msg)
else:
time.sleep(CONNECT_DELAY)
print "BluetoothDriver._init(): successfully connected to %s" % address
def shutdown(self):
self.__sock.close()
def write(self, data):
#print "BluetoothDriver.write(): data=%s" % repr(data)
size = self.__sock.send(data)
return size
def read(self, size):
data = self.__sock.recv(size)
#print "BluetoothDriver.read(): data=%s" % repr(data)
if size and not data:
raise IOError("Timeout while reading on bluetooth bus (size=%d, data=%s)" % (size, repr(data)))
else:
return data
class SerialDriver(object):
""" Driver for serial connection.
"""
def __init__(self):
try:
self._serial = serial.Serial(port=SERIAL_PORT)
self._serial.baudrate = SERIAL_BAUDRATE
self._serial.timeout = TIMEOUT
self.empty()
except:
#Logger().exception("SerialDriver._init()")
raise
def shutdown(self):
self._serial.close()
def setRTS(self, level):
""" Set RTS signal to specified level.
@param level: level to set to RTS signal
@type level: int
"""
self._serial.setRTS(level)
def setDTR(self, level):
""" Set DTR signal to specified level.
@param level: level to set to DTR signal
@type level: int
"""
self._serial.setDTR(level)
def setTimeout(self, timeout):
self._serial.timeout = timeout
def empty(self):
if hasattr(self._serial, "inWaiting"):
self.read(self._serial.inWaiting())
def write(self, data):
#Logger().debug("SerialDriver.write(): data=%s" % repr(data))
size = self._serial.write(data)
return size
def read(self, size):
data = self._serial.read(size)
#Logger().debug("SerialDriver.read(): data=%s" % repr(data))
if size and not data:
raise IOError("Timeout while reading on serial bus (size=%d, data=%s)" % (size, repr(data)))
else:
return data
class MerlinOrionHardware(object):
""" Merlin/Orion low-level hardware.
"""
def __init__(self, axis, driver):
""" Init the MerlinOrionHardware object.
"""
super(MerlinOrionHardware, self).__init__()
self.__axis = axis
self.__driver = driver
self.__encoderFullCircle = None
def __decodeAxisValue(self, strValue):
""" Decode value from axis.
Values (position, speed...) returned by axis are
32bits-encoded strings, low byte first.
@param strValue: value returned by axis
@type strValue: str
@return: value
@rtype: int
"""
value = 0
for i in xrange(3):
value += eval("0x%s" % strValue[i*2:i*2+2]) * 2 ** (i * 8)
#print "dav: %s" % value
return value
def __encodeAxisValue(self, value):
""" Encode value for axis.
Values (position, speed...) to send to axis must be
32bits-encoded strings, low byte first.
@param value: value
@type value: int
@return: value to send to axis
@rtype: str
"""
strHexValue = "000000%s" % hex(value)[2:]
strValue = strHexValue[-2:] + strHexValue[-4:-2] + strHexValue[-6:-4]
#print "value %s, eav: %s" % (value, strValue.upper())
return strValue.upper()
def __encoderToAngle(self, codPos):
""" Convert encoder value to degres.
@param codPos: encoder position
@type codPos: int
@return: position, in °
@rtype: float
"""
value = (codPos - ENCODER_ZERO) * 360. / self.__encoderFullCircle
#print "e2a: %f" % value
return value
def __angleToEncoder(self, pos):
""" Convert degres to encoder value.
@param pos: position, in °
@type pos: float
@return: encoder position
@rtype: int
"""
value = int(pos * self.__encoderFullCircle / 360. + ENCODER_ZERO)
#print "a2e: %d" % value
return value
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, self.__axis, param)
#print "cmd: %s"% cmd
#print "MerlinOrionHardware.__sendCmd(): axis %d cmd=%s" % (self.__axis, repr(cmd))
for nbTry in range(3):
try:
answer = ""
self.__driver.write(":%s\r" % cmd)
c = ''
while c not in ('=', '!'):
c = self.__driver.read(1)
#Lprint "MerlinOrionHardware.__sendCmd(): c=%s" % repr(c)
if c == '!':
c = self.__driver.read(1) # Get error code
raise IOError("Error in command %s (err=%s)" % (repr(cmd), c))
answer = ""
while True:
c = self.__driver.read(1)
#print "MerlinOrionHardware.__sendCmd(): c=%s" % repr(c)
if c == '\r':
break
answer += c
except IOError:
print "MerlinOrionHardware.__sendCmd(): axis %d can't sent command %s. Retrying..." % (self.__axis, repr(cmd))
else:
break
else:
raise HardwareError("axis %d can't send command %s" % (self.__axis, repr(cmd)))
#print "MerlinOrionHardware.__sendCmd(): axis %d ans=%s" % (self.__axis, repr(answer))
return answer
def init(self):
""" Init the MerlinOrion hardware.
Done only once per axis.
"""
# Stop motor
self.__sendCmd("L")
# Check motor?
self.__sendCmd("F")
# Get firmeware version
value = self.__sendCmd("e")
print "MerlinOrionHardware.init(): firmeware version=%s" % value
# Get encoder full circle
value = self.__sendCmd("a")
self.__encoderFullCircle = self.__decodeAxisValue(value)
print "MerlinOrionHardware.init(): encoder full circle=%s" % hex(self.__encoderFullCircle)
# Get sidereal rate
value = self.__sendCmd("D")
print "MerlinOrionHardware.init(): sidereal rate=%s" % hex(self.__decodeAxisValue(value))
def read(self):
""" Read the axis position.
@return: axis position, in °
@rtype: float
"""
value = self.__sendCmd("j")
self.__driver.releaseBus()
pos = self.__encoderToAngle(self.__decodeAxisValue(value))
return pos
def setOutput(self, state):
""" Set output state.
@param state: new state of the the output
@type state: bool
"""
self.__sendCmd("O", str(int(state)))
def drive(self, pos):
""" Drive the axis.
@param pos: position to reach, in °
@type pos: float
"""
strValue = self.__encodeAxisValue(self.__angleToEncoder(pos))
self.__sendCmd("L")
self.__sendCmd("G", "00")
#self.__sendCmd("j")
self.__sendCmd("S", strValue)
self.__sendCmd("J")
def stop(self):
""" Stop the axis.
"""
self.__sendCmd("L")
def startJog(self, dir_, speed):
""" Start the axis.
@param dir_: direction ('+', '-')
@type dir_: str
@param speed: speed
@type speed: int
"""
self.__sendCmd("L")
if dir_ == '+':
self.__sendCmd("G", "30")
elif dir_ == '-':
self.__sendCmd("G", "31")
else:
raise ValueError("Axis %d dir. must be in ('+', '-')" % self.__axis)
self.__sendCmd("I", self.__encodeAxisValue(speed))
self.__sendCmd("J")
def setSpeed(self, speed):
self.__sendCmd("I", self.__encodeAxisValue(speed))
def getStatus(self):
""" Get axis status.
@return: axis status
@rtype: str
"""
return self.__sendCmd("f")
def test():
""" Test.
"""
driver = SerialDriver()
yaw = MerlinOrionHardware(YAW_AXIS, driver)
pitch = MerlinOrionHardware(PITCH_AXIS, driver)
shutter = MerlinOrionHardware(SHUTTER_AXIS, driver)
lst = []
lst.append(Point(11.4,14.7))
lst.append(Point(14.7,1.5))
lst.append(Point(24.2,14.1))
lst.append(Point(29.4,2.0))
bez = BezierCurve(lst) # initialise the Bezier curve with control points
bez.findsteps() # find the minimum number of steps to traverse the curve
time.sleep(3) # give time to decide on the step or press Ctrl+C to quit
cp = bez.bezier() # calculate the curve points (equals to # of steps + 1)
yaw.init()
pitch.init()
for i in range(len(cp)):
cp[i].pprint()
yaw.drive(cp[i].x)
pitch.drive(cp[i].y)
while yaw.getStatus()[1] != '0':
time.sleep(0.1)
while pitch.getStatus()[1] != '0':
time.sleep(0.1)
# Stabilization delay
time.sleep(0.5)
# press the shutter 1/2 second time value
shutter.setOutput(1)
time.sleep(0.5)
shutter.setOutput(0)
# delay between shots
time.sleep(2.0)
driver.shutdown()
if __name__ == "__main__":
""" test() won't be executed if the module is imported.
"""
test()
lst.append(Point(11.4,14.7))
lst.append(Point(14.7,1.5))
lst.append(Point(24.2,14.1))
lst.append(Point(29.4,2.0))
Users browsing this forum: No registered users and 1 guest