mirror of
https://github.com/OpenSolo/OpenSolo.git
synced 2025-04-29 14:14:30 +02:00

The bug in ArduCopter master that made MSG_MOUNT_CONTROL fail to yaw the copter in smart shots has been found and fixed as of Fri 12/22. It is merged into master and begin backported to 3.5.5. As such, the workaround (simultaneous CONDITION_YAW commands) can now be removed from the smart shots. Also cleaned up the code around it a bit.
686 lines
27 KiB
Python
686 lines
27 KiB
Python
'''
|
|
follow.py
|
|
shotmanager
|
|
|
|
The Follow Me smart shot controller.
|
|
Runs as a DroneKit-Python script.
|
|
Created by Will Silva on 12/14/2015.
|
|
Altitude and Leash follow created by Jason Short 2/25/2016
|
|
|
|
Copyright (c) 2016 3D Robotics.
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
You may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
'''
|
|
|
|
import os
|
|
import math
|
|
import struct
|
|
import monotonic
|
|
import collections
|
|
import app_packet
|
|
import camera
|
|
import location_helpers
|
|
import pathHandler
|
|
import shotLogger
|
|
import shots
|
|
import socket
|
|
from dronekit import Vehicle, LocationGlobalRelative, VehicleMode
|
|
from pymavlink import mavutil
|
|
from shotManagerConstants import *
|
|
from orbitController import OrbitController
|
|
from lookAtController import LookAtController
|
|
from flyController import FlyController
|
|
from leashController import LeashController
|
|
from os import sys, path
|
|
from vector3 import Vector3
|
|
from sololink import btn_msg
|
|
sys.path.append(os.path.realpath(''))
|
|
|
|
FOLLOW_PORT = 14558
|
|
SOCKET_TIMEOUT = 0.01
|
|
|
|
DEFAULT_PILOT_VELZ_MAX_VALUE = 133.0
|
|
ZVEL_FACTOR = 0.95
|
|
|
|
# in degrees per second for FREE CAM
|
|
YAW_SPEED = 120.0
|
|
|
|
MIN_RAW_ROI_QUEUE_LENGTH = 5
|
|
MIN_FILT_ROI_QUEUE_LENGTH = 4
|
|
|
|
#Path accel/decel constants
|
|
PATH_ACCEL = 2.5
|
|
ACCEL_PER_TICK = PATH_ACCEL * UPDATE_TIME
|
|
|
|
FOLLOW_ALT_NUDGE_SPEED = 6 # m/s
|
|
FOLLOW_ALT_ROI_OFFSET = 100
|
|
FOLLOW_ALT_PADDLE_DEADZONE = 0.02
|
|
|
|
FOLLOW_YAW_SPEED = 60.0 # deg/s
|
|
FOLLOW_PITCH_SPEED = 60.0 # deg/s
|
|
|
|
ROI_ALT_FILTER_GAIN = 0.65 # Relative contribution of the previous and new data in the phone altitude filter. Higher is slower and smoother.
|
|
|
|
logger = shotLogger.logger
|
|
|
|
'''
|
|
Define the different followStates:
|
|
|
|
Follow Wait
|
|
Initial state before everything is ready. This state exits when the
|
|
first ROI is sent from the app. (in filterROI()) The shot will probably
|
|
only be in this state for a few ticks.
|
|
|
|
Look At Me
|
|
Copter does not move unless commanded by stick actions, but the camera
|
|
rotates to keep the ROI in frame
|
|
|
|
Follow Orbit: Default behaviour.
|
|
Copter translates to keep the user in frame, maintining a offset in a
|
|
particular direction (i.e. North) of the user. Once the orbit is started
|
|
(cruise via the app or with sticks) then the copter orbits around the ROI,
|
|
keeping the subject in frame.
|
|
|
|
Follow Leash
|
|
Copter tries to stay behind the user's direction of motion.
|
|
If the copter is within the leash length from the user, the leash is
|
|
"slack" so the copter rotates but does not move. Once the copter gets
|
|
further from the user. It swings around to get behind the user and keep up.
|
|
User can adjust altitude and radius with sticks but cannot strafe manually.
|
|
|
|
Follow Free Look
|
|
Copter maintains an offset from the ROI and can get dragged around as
|
|
the ROI moves. User can move the copter with the roll/pitch sticks
|
|
like FLY mode. User can adjust the altitude with the controller paddle.
|
|
User can freely control the camera pan/tilt with the left stick.
|
|
Camera maintains constant yaw/pitch unless the user permutes it.
|
|
'''
|
|
|
|
FOLLOW_WAIT = 0
|
|
FOLLOW_LOOKAT = 1
|
|
FOLLOW_ORBIT = 2
|
|
FOLLOW_LEASH = 3
|
|
FOLLOW_FREELOOK = 4
|
|
|
|
# used to manage user preferences for different follow styles
|
|
FOLLOW_PREF_HOLD_ANGLE = 0
|
|
FOLLOW_PREF_FREELOOK = 1
|
|
FOLLOW_PREF_LEASH = 2
|
|
|
|
|
|
class FollowShot():
|
|
def __init__(self, vehicle, shotmgr):
|
|
# initialize vehicle object
|
|
self.vehicle = vehicle
|
|
|
|
# initialize shotmanager object
|
|
self.shotmgr = shotmgr
|
|
|
|
# initialize pathController to None
|
|
self.pathController = None
|
|
|
|
# used to manage cruise speed and pause control for orbiting
|
|
self.pathHandler = pathHandler.PathHandler( self.vehicle, self.shotmgr )
|
|
|
|
|
|
''' Shot State '''
|
|
self.followState = FOLLOW_WAIT
|
|
|
|
self.followPreference = FOLLOW_PREF_HOLD_ANGLE
|
|
|
|
# init camera mount style
|
|
self.updateMountStatus()
|
|
|
|
|
|
''' ROI control '''
|
|
# initialize raw & filtered rois to None
|
|
self.filteredROI = None
|
|
self.rawROI = None
|
|
|
|
# initialize raw and filtered roi queues
|
|
self.rawROIQueue = collections.deque(maxlen=MIN_RAW_ROI_QUEUE_LENGTH)
|
|
self.filteredROIQueue = collections.deque(maxlen=MIN_FILT_ROI_QUEUE_LENGTH)
|
|
|
|
# initialize roiVelocity to None
|
|
self.roiVelocity = None
|
|
|
|
# for limiting follow acceleration could lead to some bad lag
|
|
self.translateVel = Vector3()
|
|
|
|
|
|
''' Altitude Limit'''
|
|
# get maxClimbRate and maxAltitude from APM params
|
|
self.maxClimbRate = shotmgr.getParam( "PILOT_VELZ_MAX", DEFAULT_PILOT_VELZ_MAX_VALUE ) / 100.0 * ZVEL_FACTOR
|
|
self.maxAlt = self.shotmgr.getParam( "FENCE_ALT_MAX", DEFAULT_FENCE_ALT_MAX )
|
|
logger.log("[follow]: Maximum altitude stored: %f" % self.maxAlt)
|
|
|
|
# check APM params to see if Altitude Limit should apply
|
|
if self.shotmgr.getParam( "FENCE_ENABLE", DEFAULT_FENCE_ENABLE ) == 0:
|
|
self.maxAlt = None
|
|
logger.log("[Follow Me]: Altitude Limit disabled.")
|
|
|
|
|
|
# the open loop altitude of the follow controllers, relative to the ROI
|
|
self.followControllerAltOffset = 0
|
|
|
|
# used to adjust frame of ROI vertically (Just affects camera pointing, not copter position)
|
|
self.ROIAltitudeOffset = 0
|
|
|
|
|
|
''' Communications '''
|
|
# configure follow socket
|
|
self.setupSocket()
|
|
|
|
# take away user control of vehicle
|
|
self.vehicle.mode = VehicleMode("GUIDED")
|
|
self.shotmgr.rcMgr.enableRemapping( True )
|
|
|
|
|
|
# channels are expected to be floating point values in the (-1.0, 1.0) range
|
|
def handleRCs( self, channels ):
|
|
|
|
# check socket for a new ROI from the app
|
|
self.checkSocket()
|
|
|
|
# if we have never received an ROI
|
|
if not self.rawROI:
|
|
return
|
|
|
|
# smooth ROI and calculate translateVel for follow
|
|
self.filterROI()
|
|
|
|
# if we are not warmed up, dont do anything
|
|
if self.followState == FOLLOW_WAIT:
|
|
return
|
|
|
|
'''
|
|
Position Control
|
|
Note: All follow controllers return position and velocity of the
|
|
drone in "absolute" space (as opposed to relative to the ROI)
|
|
Pos: Lat, lon, alt. Alt is relative to home location. NEU frame..
|
|
Vel: Speed in the x,y, and z directions. NEU frame. vel.z needs
|
|
to be inverted before passing to the autopilot.
|
|
'''
|
|
|
|
# Look At Me Mode (Vehicle stays put)
|
|
if self.followState == FOLLOW_LOOKAT:
|
|
pos, vel = self.pathController.move(channels)
|
|
|
|
# Free Look Follow Mode (Vehicle controls are similar to FLY)
|
|
elif self.followState == FOLLOW_FREELOOK:
|
|
pos, vel = self.pathController.move(channels, newHeading = self.camYaw, newOrigin = self.filteredROI, roiVel = self.translateVel)
|
|
|
|
# Follow Me Mode (Vehicle controls are similar to ORBIT)
|
|
elif self.followState == FOLLOW_ORBIT:
|
|
pos, vel = self.pathController.move(channels, cruiseSpeed = self.pathHandler.cruiseSpeed, newroi = self.filteredROI, roiVel = self.translateVel)
|
|
|
|
elif self.followState == FOLLOW_LEASH:
|
|
pos, vel = self.pathController.move(channels, newroi = self.filteredROI, roiVel = self.translateVel)
|
|
|
|
|
|
# learn any changes to controller alt offset to apply it to other controllers when instantiated (to avoid jerks)
|
|
self.followControllerAltOffset = pos.alt - self.filteredROI.alt
|
|
|
|
# mavlink expects 10^7 integer for accuracy
|
|
latInt = int(pos.lat * 10000000)
|
|
lonInt = int(pos.lon * 10000000)
|
|
|
|
# Convert from NEU to NED to send to autopilot
|
|
vel.z *= -1
|
|
|
|
# create the SET_POSITION_TARGET_GLOBAL_INT command
|
|
msg = self.vehicle.message_factory.set_position_target_global_int_encode(
|
|
0, # time_boot_ms (not used)
|
|
0, 1, # target system, target component
|
|
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT, # frame
|
|
0b0000110111000000, # Pos Vel type_mask
|
|
latInt, lonInt, pos.alt, # x, y, z positions
|
|
vel.x, vel.y, vel.z, # x, y, z velocity in m/s
|
|
0, 0, 0, # x, y, z acceleration (not used)
|
|
0, 0) # yaw, yaw_rate (not used)
|
|
|
|
# send command to vehicle
|
|
self.vehicle.send_mavlink(msg)
|
|
|
|
|
|
''' Pointing Control'''
|
|
|
|
# If followState mandates that the user controls the pointing
|
|
if self.followState == FOLLOW_FREELOOK:
|
|
self.manualPitch(channels[THROTTLE])
|
|
self.manualYaw(channels)
|
|
self.handleFreeLookPointing()
|
|
|
|
# If followState mandates that the copter should point at the ROI
|
|
else:
|
|
# Adjust the height of the ROI using the paddle
|
|
# we pass in both the filtered gimbal paddle value as well as the raw one
|
|
self.updateROIAltOffset(channels[RAW_PADDLE])
|
|
# we make a copy of the ROI to allow us to add in an altitude offset
|
|
# this means the ROI doesn't get polluted with the alt nudging
|
|
tempROI = LocationGlobalRelative(self.filteredROI.lat, self.filteredROI.lon, self.filteredROI.alt)
|
|
self.handleLookAtPointing(tempROI)
|
|
|
|
|
|
# moves an offset of the ROI altitude up or down
|
|
def updateROIAltOffset(self, rawPaddleValue):
|
|
# no gimbal, no reason to change ROI
|
|
if self.vehicle.mount_status[0] == None:
|
|
return
|
|
|
|
if abs(rawPaddleValue) > FOLLOW_ALT_PADDLE_DEADZONE:
|
|
self.ROIAltitudeOffset += (rawPaddleValue * FOLLOW_ALT_NUDGE_SPEED * UPDATE_TIME)
|
|
|
|
# if we can handle the button we do
|
|
def handleButton(self, button, event):
|
|
|
|
if button == btn_msg.ButtonA and event == btn_msg.ClickRelease:
|
|
|
|
# Allow the user to exit Look At Me mode (into the previous follow mode) with the A button
|
|
if self.followPreference == FOLLOW_PREF_LEASH:
|
|
self.initState(FOLLOW_LEASH)
|
|
elif self.followPreference == FOLLOW_PREF_FREELOOK:
|
|
self.initState(FOLLOW_FREELOOK)
|
|
else:
|
|
self.initState(FOLLOW_ORBIT)
|
|
|
|
# Change Follow Mode to Look At Me on Pause button press
|
|
if button == btn_msg.ButtonLoiter and event == btn_msg.ClickRelease:
|
|
self.initState(FOLLOW_LOOKAT)
|
|
self.shotmgr.notifyPause(True)
|
|
self.updateAppOptions()
|
|
|
|
self.setButtonMappings()
|
|
|
|
|
|
def setButtonMappings(self):
|
|
buttonMgr = self.shotmgr.buttonManager
|
|
|
|
# Allow the user to exit Look At Me mode (into the previous follow mode) with the A button
|
|
if self.followState == FOLLOW_LOOKAT:
|
|
buttonMgr.setArtooButton(btn_msg.ButtonA, shots.APP_SHOT_FOLLOW, btn_msg.ARTOO_BITMASK_ENABLED, "Resume\0")
|
|
else:
|
|
buttonMgr.setArtooButton(btn_msg.ButtonA, shots.APP_SHOT_FOLLOW, 0, "\0")
|
|
|
|
|
|
buttonMgr.setArtooButton(btn_msg.ButtonB, shots.APP_SHOT_FOLLOW, 0, "\0")
|
|
|
|
|
|
|
|
# pass in the value portion of the SOLO_FOLLOW_OPTIONS packet
|
|
def handleOptions(self, options, version):
|
|
if version == 1:
|
|
logger.log("[Follow Me]: Received Follow Me Options v1 packet.")
|
|
(cruiseSpeed, _lookat) = struct.unpack('<fi', options)
|
|
|
|
elif version == 2:
|
|
logger.log("[Follow Me]: Received Follow Me Options v2 packet.")
|
|
(cruiseSpeed, _lookat, self.followPreference) = struct.unpack('<fii', options)
|
|
else:
|
|
logger.log("[follow]: Unknown packet version requested.")
|
|
return
|
|
|
|
logger.log( "[Follow Me]: -->Cruise speed: %f" % cruiseSpeed)
|
|
logger.log( "[Follow Me]: -->Look At: %i" % _lookat)
|
|
logger.log( "[Follow Me]: -->Follow Pref: %i" % self.followPreference)
|
|
|
|
self.pathHandler.setCruiseSpeed(cruiseSpeed)
|
|
|
|
# if user presses cruise arrows, force user into Orbit mode
|
|
if cruiseSpeed != 0:
|
|
# if we press cruise, force user into Orbit mode
|
|
# only work on state changes
|
|
# force follow Pref into Orbit
|
|
self.followPreference = FOLLOW_PREF_HOLD_ANGLE
|
|
newState = FOLLOW_ORBIT
|
|
|
|
elif _lookat == 1:
|
|
# Lookat overrides the follow preferences
|
|
newState = FOLLOW_LOOKAT
|
|
else:
|
|
# we may be exiting lookat into Orbit, Freelook or Leash
|
|
if self.followPreference == FOLLOW_PREF_FREELOOK:
|
|
newState = FOLLOW_FREELOOK
|
|
elif self.followPreference == FOLLOW_PREF_LEASH:
|
|
newState = FOLLOW_LEASH
|
|
else:
|
|
# enter default state
|
|
newState = FOLLOW_ORBIT
|
|
|
|
self.initState(newState)
|
|
|
|
|
|
def initState(self, newState):
|
|
'''Manages state changes'''
|
|
# Don't change state until we've received at least one ROI from phone
|
|
# Don't re-init previous state
|
|
if not self.rawROI or self.followState == newState:
|
|
return
|
|
|
|
self.followState = newState
|
|
|
|
if self.followState == FOLLOW_LOOKAT:
|
|
logger.log("[Follow Me]: enter Lookat")
|
|
self.initLookAtMeController()
|
|
|
|
elif self.followState == FOLLOW_ORBIT:
|
|
logger.log("[Follow Me]: enter Orbit")
|
|
self.initOrbitController()
|
|
|
|
elif self.followState == FOLLOW_LEASH:
|
|
logger.log("[Follow Me]: enter Leash")
|
|
self.initLeashController()
|
|
|
|
elif self.followState == FOLLOW_FREELOOK:
|
|
logger.log("[Follow Me]: enter Freelook")
|
|
self.initFreeLookController()
|
|
|
|
self.updateMountStatus()
|
|
|
|
# update UI
|
|
self.updateAppOptions()
|
|
self.setButtonMappings()
|
|
|
|
|
|
# send our current set of options to the app
|
|
def updateAppOptions(self):
|
|
|
|
_lookAtMe = self.followState == FOLLOW_LOOKAT
|
|
|
|
packetV1 = struct.pack('<IIfi', app_packet.SOLO_FOLLOW_OPTIONS, 8, self.pathHandler.cruiseSpeed, _lookAtMe)
|
|
self.shotmgr.appMgr.sendPacket(packetV1)
|
|
|
|
packetV2 = struct.pack('<IIfii', app_packet.SOLO_FOLLOW_OPTIONS_V2, 12, self.pathHandler.cruiseSpeed, _lookAtMe, self.followPreference)
|
|
self.shotmgr.appMgr.sendPacket(packetV2)
|
|
|
|
def setupSocket(self):
|
|
'''Sets up the socket for GPS data from app'''
|
|
|
|
# Follow me creates a socket to get new ROIs
|
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
self.socket.setblocking(0)
|
|
try:
|
|
self.socket.bind(("", FOLLOW_PORT))
|
|
except Exception as e:
|
|
logger.log("[follow]: failed to bind follow socket - %s"%(type(e)))
|
|
|
|
self.socket.settimeout(SOCKET_TIMEOUT)
|
|
|
|
|
|
def checkSocket(self):
|
|
'''check our socket to see if a new follow roi is there'''
|
|
|
|
packetsConsumed = 0
|
|
#consume from socket until it's empty
|
|
while True:
|
|
try:
|
|
data, addr = self.socket.recvfrom(28)
|
|
except socket.timeout:
|
|
break
|
|
else:
|
|
newestData = data
|
|
packetsConsumed += 1
|
|
|
|
# make sure we have a packet to work with
|
|
if packetsConsumed > 0:
|
|
(id, length, lat, lon, alt) = struct.unpack('<IIddf', newestData)
|
|
if id == app_packet.SOLO_MESSAGE_LOCATION:
|
|
now = monotonic.monotonic()
|
|
if self.rawROI is None:
|
|
self.roiDeltaTime = None
|
|
else:
|
|
self.roiDeltaTime = now - self.previousROItime
|
|
self.previousROItime = now
|
|
self.rawROI = LocationGlobalRelative(lat,lon,alt)
|
|
else:
|
|
logger.log("[follow]: got an invalid packet from follow socket")
|
|
else:
|
|
pass # will implement 2 second timeout to kill shot here
|
|
|
|
def filterROI(self):
|
|
'''Filters app ROI using a 5th order linear filter and calculates an associated roi velocity'''
|
|
|
|
# store last 5 raw roi values
|
|
self.rawROIQueue.append(self.rawROI)
|
|
|
|
# only run filter if we have enough data in the queues.
|
|
if len(self.rawROIQueue) == MIN_RAW_ROI_QUEUE_LENGTH and len(self.filteredROIQueue) == MIN_FILT_ROI_QUEUE_LENGTH:
|
|
num = [0,0.000334672774973874,0.00111965413719632,-0.000469533537393159,-0.000199779184127412]
|
|
den = [1,-3.48113699710809,4.56705782792063,-2.67504447769757,0.589908661075676]
|
|
|
|
filteredLat = ( (num[4]*self.rawROIQueue[0].lat + num[3]*self.rawROIQueue[1].lat + num[2]*self.rawROIQueue[2].lat + num[1]*self.rawROIQueue[3].lat + num[0]*self.rawROIQueue[4].lat) - (den[4]*self.filteredROIQueue[0].lat + den[3]*self.filteredROIQueue[1].lat + den[2]*self.filteredROIQueue[2].lat + den[1]*self.filteredROIQueue[3].lat) ) / den[0]
|
|
filteredLon = ( (num[4]*self.rawROIQueue[0].lon + num[3]*self.rawROIQueue[1].lon + num[2]*self.rawROIQueue[2].lon + num[1]*self.rawROIQueue[3].lon + num[0]*self.rawROIQueue[4].lon) - (den[4]*self.filteredROIQueue[0].lon + den[3]*self.filteredROIQueue[1].lon + den[2]*self.filteredROIQueue[2].lon + den[1]*self.filteredROIQueue[3].lon) ) / den[0]
|
|
filteredAlt = ROI_ALT_FILTER_GAIN * self.filteredROIQueue[-1].alt + (1-ROI_ALT_FILTER_GAIN) * self.rawROI.alt# index -1 should refer to most recent value in the queue.
|
|
self.filteredROI = LocationGlobalRelative(filteredLat,filteredLon,filteredAlt)
|
|
else:
|
|
self.filteredROI = self.rawROI
|
|
|
|
|
|
# store last 4 filtered roi values
|
|
self.filteredROIQueue.append(self.filteredROI)
|
|
|
|
# only called once - when we have an ROI from phone
|
|
if len(self.filteredROIQueue) == 1:
|
|
# initialize ROI velocity for Guided controller
|
|
self.roiVelocity = Vector3(0,0,0)
|
|
|
|
# initialize the altitude maintained for each controller
|
|
# the first ROI sent from the app is supposed to have 0 altitude, but in case it doesn't subtract it out.
|
|
self.followControllerAltOffset = self.vehicle.location.global_relative_frame.alt - self.rawROI.alt
|
|
logger.log('[Follow Me]: First ROI received from app has altitude: %f m' %self.rawROI.alt)
|
|
|
|
|
|
# go into Look At Me as default state. iOS app changes state to Orbit after 3 seconds.
|
|
self.initState(FOLLOW_LOOKAT)
|
|
|
|
elif len(self.filteredROIQueue) > 1 and self.roiDeltaTime:
|
|
#calculate vector from previous to new roi in North,East,Up
|
|
vec = location_helpers.getVectorFromPoints(self.filteredROIQueue[-2] , self.filteredROIQueue[-1])
|
|
|
|
# calculate velocity based on time difference
|
|
# roiVelocity in NEU frame
|
|
self.roiVelocity = vec/self.roiDeltaTime
|
|
|
|
# calculate desiredYaw and desiredPitch from new ROI
|
|
self.desiredYaw, self.desiredPitch = location_helpers.calcYawPitchFromLocations(self.vehicle.location.global_relative_frame, self.filteredROI)
|
|
|
|
|
|
#x component accel limit
|
|
if self.roiVelocity.x > self.translateVel.x:
|
|
self.translateVel.x += ACCEL_PER_TICK
|
|
self.translateVel.x = min(self.translateVel.x, self.roiVelocity.x)
|
|
elif self.roiVelocity.x < self.translateVel.x:
|
|
self.translateVel.x -= ACCEL_PER_TICK
|
|
self.translateVel.x = max(self.translateVel.x, self.roiVelocity.x)
|
|
|
|
#y component accel limit
|
|
if self.roiVelocity.y > self.translateVel.y:
|
|
self.translateVel.y += ACCEL_PER_TICK
|
|
self.translateVel.y = min(self.translateVel.y, self.roiVelocity.y)
|
|
elif self.roiVelocity.y < self.translateVel.y:
|
|
self.translateVel.y -= ACCEL_PER_TICK
|
|
self.translateVel.y = max(self.translateVel.y, self.roiVelocity.y)
|
|
|
|
#z component accel limit
|
|
if self.roiVelocity.z > self.translateVel.z:
|
|
self.translateVel.z += ACCEL_PER_TICK
|
|
self.translateVel.z = min(self.translateVel.z, self.roiVelocity.z)
|
|
elif self.roiVelocity.z < self.translateVel.z:
|
|
self.translateVel.z -= ACCEL_PER_TICK
|
|
self.translateVel.z = max(self.translateVel.z, self.roiVelocity.z)
|
|
|
|
|
|
def initOrbitController(self):
|
|
'''Resets the controller'''
|
|
|
|
# reset Orbit
|
|
resetRadius = location_helpers.getDistanceFromPoints(self.filteredROI, self.vehicle.location.global_relative_frame)
|
|
resetAz = location_helpers.calcAzimuthFromPoints(self.filteredROI, self.vehicle.location.global_relative_frame)
|
|
|
|
# Initialize the feed-forward orbit controller
|
|
self.pathController = OrbitController(self.filteredROI, resetRadius, resetAz, self.followControllerAltOffset)
|
|
|
|
# set controller options
|
|
self.pathController.setOptions(maxClimbRate = self.maxClimbRate, maxAlt = self.maxAlt)
|
|
|
|
def initLeashController(self):
|
|
'''Resets the controller'''
|
|
# reset leash
|
|
resetRadius = location_helpers.getDistanceFromPoints(self.filteredROI, self.vehicle.location.global_relative_frame)
|
|
resetAz = location_helpers.calcAzimuthFromPoints(self.filteredROI, self.vehicle.location.global_relative_frame)
|
|
|
|
# Initialize the feed-forward orbit controller
|
|
self.pathController = LeashController(self.filteredROI, resetRadius, resetAz, self.followControllerAltOffset)
|
|
|
|
# set controller options
|
|
|
|
self.pathController.setOptions(maxClimbRate = self.maxClimbRate, maxAlt = self.maxAlt)
|
|
|
|
|
|
def initFreeLookController(self):
|
|
'''Enter/exit free look'''
|
|
|
|
vectorOffset = location_helpers.getVectorFromPoints(self.filteredROI, self.vehicle.location.global_relative_frame)
|
|
xOffset = vectorOffset.x
|
|
yOffset = vectorOffset.y
|
|
zOffset = self.followControllerAltOffset
|
|
|
|
heading = camera.getYaw(self.vehicle)
|
|
|
|
# Initialize the feed-forward orbit controller
|
|
self.pathController = FlyController(self.filteredROI, xOffset, yOffset, zOffset, heading)
|
|
|
|
# set controller options
|
|
self.pathController.setOptions(maxClimbRate = self.maxClimbRate, maxAlt = self.maxAlt)
|
|
|
|
# default camPitch and Yaw from vehicle
|
|
self.camPitch = camera.getPitch(self.vehicle)
|
|
self.camYaw = camera.getYaw(self.vehicle)
|
|
|
|
# only used for non-gimbaled copters
|
|
self.camDir = 1
|
|
|
|
def initLookAtMeController(self):
|
|
'''Enter lookat mode'''
|
|
# zero out any cruise speed
|
|
self.pathHandler.pause()
|
|
|
|
# Initialize the feed-forward orbit controller. Look at me is unique in that we pass total altitude, not just the offset
|
|
self.pathController = LookAtController(self.vehicle, self.followControllerAltOffset + self.filteredROI.alt)
|
|
|
|
# set controller options
|
|
self.pathController.setOptions(maxClimbRate = self.maxClimbRate, maxAlt = self.maxAlt)
|
|
|
|
def updateMountStatus(self):
|
|
if self.followState == FOLLOW_FREELOOK:
|
|
msg = self.vehicle.message_factory.mount_configure_encode(
|
|
0, 1, # target system, target component
|
|
mavutil.mavlink.MAV_MOUNT_MODE_MAVLINK_TARGETING, #mount_mode
|
|
1, # stabilize roll
|
|
1, # stabilize pitch
|
|
1, # stabilize yaw
|
|
)
|
|
else:
|
|
# set gimbal targeting mode
|
|
msg = self.vehicle.message_factory.mount_configure_encode(
|
|
0, 1, # target system, target component
|
|
mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT, #mount_mode
|
|
1, # stabilize roll
|
|
1, # stabilize pitch
|
|
1, # stabilize yaw
|
|
)
|
|
self.vehicle.send_mavlink(msg)
|
|
|
|
|
|
def manualPitch(self, stick):
|
|
self.camPitch += stick * FOLLOW_PITCH_SPEED * UPDATE_TIME
|
|
|
|
if self.camPitch > 0.0:
|
|
self.camPitch = 0.0
|
|
elif self.camPitch < -90:
|
|
self.camPitch = -90
|
|
|
|
|
|
def manualYaw(self, channels):
|
|
if channels[YAW] == 0:
|
|
return
|
|
self.camYaw += channels[YAW] * FOLLOW_YAW_SPEED * UPDATE_TIME
|
|
if channels[YAW] > 0:
|
|
self.camDir = 1
|
|
else:
|
|
self.camDir = -1
|
|
|
|
self.camYaw = location_helpers.wrapTo360(self.camYaw)
|
|
|
|
def handleFreeLookPointing(self):
|
|
# if we do have a gimbal, use mount_control to set pitch and yaw
|
|
if self.vehicle.mount_status[0] is not None:
|
|
msg = self.vehicle.message_factory.mount_control_encode(
|
|
0, 1, # Target system, target component
|
|
self.camPitch * 100, # Pitch in centidegrees
|
|
0.0, # Roll not used
|
|
self.camYaw * 100, # Yaw in centidegrees
|
|
0 # save position
|
|
)
|
|
self.vehicle.send_mavlink(msg)
|
|
|
|
else:
|
|
# if we don't have a gimbal, just set CONDITION_YAW
|
|
msg = self.vehicle.message_factory.command_long_encode(
|
|
0, 1, # target system, target component
|
|
mavutil.mavlink.MAV_CMD_CONDITION_YAW, #command
|
|
0, # confirmation
|
|
self.camYaw, # param 1 - target angle
|
|
YAW_SPEED, # param 2 - yaw speed
|
|
self.camDir, # param 3 - direction
|
|
0.0, # relative offset
|
|
0, 0, 0 # params 5-7 (unused)
|
|
)
|
|
self.vehicle.send_mavlink(msg)
|
|
|
|
|
|
def handleLookAtPointing(self, tempROI):
|
|
# set ROI
|
|
msg = self.vehicle.message_factory.command_int_encode(
|
|
0, 1, # target system, target component
|
|
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT, #frame
|
|
mavutil.mavlink.MAV_CMD_DO_SET_ROI, #command
|
|
0, #current
|
|
0, #autocontinue
|
|
0, 0, 0, 0, #params 1-4
|
|
tempROI.lat*1.E7,
|
|
tempROI.lon*1.E7,
|
|
tempROI.alt + self.ROIAltitudeOffset #offset for ROI
|
|
)
|
|
|
|
# send pointing message
|
|
self.vehicle.send_mavlink(msg)
|
|
|
|
def handlePacket(self, packetType, packetLength, packetValue):
|
|
try:
|
|
if packetType == app_packet.SOLO_FOLLOW_OPTIONS:
|
|
logger.log("[follow]: Received Follow Me Options v1 packet.")
|
|
self.handleOptions(packetValue, version=1)
|
|
|
|
elif packetType == app_packet.SOLO_FOLLOW_OPTIONS_V2:
|
|
logger.log("[follow]: Received Follow Me Options v2 packet.")
|
|
self.handleOptions(packetValue, version=2)
|
|
|
|
else:
|
|
return False
|
|
except Exception as e:
|
|
logger.log('[follow]: Error handling packet. (%s)' % e)
|
|
return False
|
|
else:
|
|
return True
|
|
|