示例#1
0
class DroneMaster(DroneVideo, FlightstatsReceiver):
    def __init__(self):

        # getting access to elements in DroneVideo and FlightstatsReciever
        super(DroneMaster, self).__init__()

        self.objectName = "NASA Airplane"
        self.startingAngle = 0

        # backpack: 120/-55/95
        # +100 for big objects, +50 for shorter objects. (Modifies how close drone is to object; smaller # > closer)
        # +10 for very small objects.
        self.yOffset = 0
        # -30 for big objects, -15 for shorter objects. (Modifies how close drone is to object; larger # > closer)
        self.ySizeOffset = -30
        # +75 for tall objects or using cube, 0 for shorter objects. (Modifies how high the drone should fly; smaller # > lower
        self.zOffset = 25

        # Seting up a timestamped folder inside Flight_Info that will have the pictures & log of this flight
        self.droneRecordPath = (
            expanduser("~") +
            "/drone_workspace/src/ardrone_lab/src/Flight_Info/" +
            datetime.datetime.now().strftime("%m-%d-%Y__%H:%M:%S, %A") +
            "_Flight_" + self.objectName + "/")
        if not os.path.exists(self.droneRecordPath):
            os.makedirs(self.droneRecordPath)
        #self.logger = Logger(self.droneRecordPath, "AR Drone Flight")
        #self.logger.Start()

        #import PID and color constants
        self.settingsPath = expanduser(
            "~"
        ) + "/drone_workspace/src/ardrone_lab/src/resources/calibrater_settings.txt"

        # initalizing the state machine that will handle which algorithms to run at which time;
        # the results of the algorithms will be used to control the drone
        self.stateMachine = StateMachine()

        # drone starts without any machine loaded, so that it can be controlled using the keyboard
        self.currMachine = None

        # initalizing helper objects
        self.pictureManager = PictureManager(self.droneRecordPath)
        self.controller = BasicDroneController("TraceCircle")
        self.startTimer = time.clock()
        # max height of drone, in mm; any higher and the drone will auto-land
        self.maxHeight = 2530
        self.enableEmergency = False
        self.emergency = False
        self.captureRound = 0.5
        self.oldBattery = -1
        self.photoDirective = None

    # Each state machine that drone mastercan use is defined here;
    # When key is pressed, define the machine to be used and switch over to it.
    # Machines are defined as array of tuples. Each tuple represents a state's directive and duration
    # in the format (directive, stateduration, errorDirective).
    # ErrorDirectives are optional, so it can be just in the format
    # (directive, stateduration);
    # directive & errorDirective must subclass AbstractDroneDirective.
    def KeyListener(self):

        key = cv2.waitKey(1) & 0xFF

        # hover over orange
        if key == ord('1'):

            self.moveTime = 0.04
            self.waitTime = 0

            pidDirective = PIDHoverColorDirective2("orange")
            pidDirective.Reset()
            alg = [(pidDirective, 6)]
            #rospy.logwarn("test3")
            #alg = [(HoverColorDirective("orange"),6)]

            algCycles = -1

            self.MachineSwitch(None, alg, algCycles, None,
                               HOVER_ORANGE_MACHINE)

        # take picture
        if key == ord('3'):

            pictureName = self.pictureManager.Capture(self.cv_image)
            rospy.logwarn("Saved picture")

        # toggle camera
        elif key == ord('c'):

            self.controller.ToggleCamera()
            rospy.logwarn("Toggled Camera")

        # land (32 => spacebar)
        elif key == 32:

            self.controller.SendLand()
            rospy.logwarn(
                "***______--______-_***Landing Drone***_-______--_____***")
            # if there is a photo directive running, save pictures just in case
            self.SaveCachePictures()

        # save all pictures in cache
        elif key == ord('s'):
            self.SaveCachePictures()

        elif key == ord('q') or key == ord('t'):

            # main algorithm components
            self.moveTime = 0.20
            self.waitTime = 0.10
            flightAltitude = 1360 + self.zOffset
            objectAltitude = 1360 + self.zOffset

            angles = 8
            # ~30 for big objects, ~ for small objects (can-sized)
            heightTolerance = 32
            orangePlatformErrHoriz = (ReturnToColorDirective(
                'orange', "blue", speedModifier=0.85), 4)
            orangePlatformErrParallel = (ReturnToColorDirective(
                'orange', "pink", speedModifier=0.85), 4)
            blueLineErr = (ReturnToLineDirective('blue',
                                                 speedModifier=0.85), 6)
            self.SaveCachePictures()
            self.photoDirective = CapturePhotoDirective(
                self.droneRecordPath, 30, 0.014, self.objectName, angles,
                objectAltitude, self.startingAngle)

            # 0.018

            alg = [(OrientLineDirective('PARALLEL', 'pink', 'orange',
                                        flightAltitude, heightTolerance,
                                        self.yOffset, self.ySizeOffset),
                    1, orangePlatformErrParallel),
                   (SetCameraDirective("FRONT"), 1),
                   (IdleDirective("Pause for setting camera to front"), 25),
                   (self.photoDirective, 1), (SetCameraDirective("BOTTOM"), 1),
                   (IdleDirective("Pause for setting camera to bottom"), 25),
                   (OrientLineDirective('PERPENDICULAR', 'blue', 'orange',
                                        flightAltitude), 5,
                    orangePlatformErrHoriz),
                   (FollowLineDirective('blue', speed=0.09), 6, blueLineErr)]

            # land on the 8th angle
            end = [(OrientLineDirective('PARALLEL', 'pink', 'orange',
                                        flightAltitude, heightTolerance,
                                        self.yOffset, self.ySizeOffset),
                    1, orangePlatformErrParallel),
                   (SetCameraDirective("FRONT"), 1),
                   (IdleDirective("Pause for setting camera to bottom"), 25),
                   (self.photoDirective, 1), (LandDirective(), 1),
                   (IdleDirective("Pause for land"), 25),
                   (self.photoDirective, 1, None, "SavePhotos")]

            if key == ord('q'):

                #doesn't auto takeoff

                self.MachineSwitch(None, alg, angles - 1, end,
                                   AUTO_CIRCLE_MACHINE)

            if key == ord('t'):

                # does the entire circle algorithm, in order; takes off by itself

                init = [
                    (SetupDirective(), 1),
                    (IdleDirective("Pause for setup"), 10),
                    (FlatTrimDirective(), 1),
                    (IdleDirective("Pause for flat trim"), 10),
                    (SetCameraDirective("BOTTOM"), 1),
                    (IdleDirective(" Pause for seting camera"), 10),
                    (TakeoffDirective(), 1),
                    (IdleDirective("Pause for takeoff"), 120),
                    (ReturnToOriginDirective('orange', 50), 7),
                    (FindPlatformAltitudeDirective('orange',
                                                   flightAltitude + 200), 5),
                    #( ReachAltitudeDirective(flightAltitude, 85), 2)
                ]

                self.MachineSwitch(init, alg, angles - 1, end,
                                   AUTO_CIRCLE_MACHINE)

        # just contains a machine to test any particular directive
        elif key == ord('b'):

            self.moveTime = 0.04
            self.waitTime = 0.14

            error = (ReturnToLineDirective('blue', speedModifier=0.4), 10)
            blueLineErr = (ReturnToLineDirective('blue'), 10)
            #orangePlatformErr = (ReturnToColorDirective('orange'), 10)
            orangePlatformErr = None
            orangePlatformErrHoriz = (ReturnToColorDirective('orange',
                                                             "blue"), 10)

            #testalg = ( OrientLineDirective( 'PARALLEL', 'pink', 'orange', 1360, 150, self.yOffset, self.ySizeOffset), 1, orangePlatformErr)
            #testalg = ( PIDOrientLineDirective( 'PERPENDICULAR', 'blue', 'orange', self.settingsPath ), 4, error)
            #testalg = ( FollowLineDirective('blue', speed = 0.25), 6, blueLineErr)
            #testalg = ( OrientLineDirective('PERPENDICULAR', 'blue', 'orange', 700), 8, orangePlatformErrHoriz)
            #testalg = ( CapturePhotoDirective(self.droneRecordPath, 10, 0.3), 1 )
            testalg = (MultiCenterTestDirective("orange"), 6)

            algCycles = -1

            alg = [testalg]

            self.MachineSwitch(None, alg, algCycles, None, TEST_MACHINE)

    # Taking in some machine's definition of states and a string name,
    # provides a "switch" for loading and removing the machines that
    # drone master uses to control the drone
    def MachineSwitch(self, newMachineInit, newMachineAlg, newMachineAlgCycles,
                      newMachineEnd, newMachineName):

        # pause the current state
        self.controller.SetCommand(0, 0, 0, 0)

        oldMachine = self.currMachine
        # if the current machine is toggled again with the same machine,
        # remove the machine and return back to the idle
        if oldMachine == newMachineName:
            self.currMachine = None

        # Otherwise, just switch to the machine
        else:
            self.stateMachine.DefineMachine(newMachineInit, newMachineAlg,
                                            newMachineAlgCycles, newMachineEnd,
                                            self)
            self.currMachine = newMachineName

        rospy.logwarn('======= Drone Master: Changing from the "' +
                      str(oldMachine) + '" machine to the "' +
                      str(self.currMachine) + '" machine =======')

    # This is called every time a frame (in self.cv_image) is updated.
    # Runs an iteration of the current state machine to get the next set of instructions, depending on the
    # machine's current state.
    def ReceivedVideo(self):

        # checks altitude; if it is higher than allowed, then drone will land
        currHeightReg = self.flightInfo["altitude"][1]
        if currHeightReg == '?':
            currHeightReg = 0

        currHeightCalc = self.flightInfo["SVCLAltitude"][1]

        if currHeightCalc != -1:
            height = currHeightCalc
        else:
            height = currHeightReg

        #rospy.logwarn( "reg: " + str(currHeightReg) + " Calc: " + str(currHeightCalc) +
        #" avg: " + str(height))

        if self.enableEmergency and height > self.maxHeight:
            self.controller.SendLand()
            self.emergency = True

        if self.emergency:
            rospy.logwarn("***** EMERGENCY LANDING: DRONE'S ALTITUDE IS " +
                          str(height) + " mm; MAX IS " + str(self.maxHeight) +
                          " mm *****")
            rospy.logwarn(
                "***______--______-_***Landing Drone***_-______--_____***")
            # if there is a photo directive running, save pictures just in case
            if self.photoDirective != None:
                self.photoDirective.SavePhotos(None, None)
                self.photoDirective = None

        # If no machine is loaded, then drone master does nothing
        # (so that the drone may be controlled with the keyboard)
        if self.currMachine == None:

            pass

        else:
            # retrieving the instructions for whatever state the machine is in
            # and commanding the drone to move accordingly
            droneInstructions, segImage, moveTime, waitTime = self.stateMachine.GetUpdate(
                self.cv_image, self.flightInfo)
            self.cv_image = segImage
            self.MoveFixedTime(droneInstructions[0], droneInstructions[1],
                               droneInstructions[2], droneInstructions[3],
                               moveTime, waitTime)

        # draws battery display and height for info Window

        color = (255, 255, 255)
        if self.flightInfo["batteryPercent"][1] != "?":

            batteryPercent = int(self.flightInfo["batteryPercent"][1])

            sum = int(batteryPercent * .01 * (255 + 255))
            if sum > 255:
                base = 255
                overflow = sum - 255
            else:
                base = sum
                overflow = 0

            green = base
            red = 255 - overflow

            color = (0, green, red)

            batteryPercent = str(batteryPercent) + "%"

            if self.flightInfo["batteryPercent"][1] != self.oldBattery:
                self.oldBattery = self.flightInfo["batteryPercent"][1]
                self.info = np.zeros((70, 100, 3), np.uint8)
                cv2.putText(self.info, batteryPercent, (10, 40),
                            cv2.FONT_HERSHEY_SIMPLEX, 1, color, 1, cv2.LINE_AA)

        #cv2.putText(self.info, str(int(height)) + " mm",
        #(50,120), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255),1,cv2.LINE_AA)

    # this function will go a certain speed for a set amount of time, then rest for wait_time # of cycles
    def MoveFixedTime(self, xSpeed, ySpeed, yawSpeed, zSpeed, move_time,
                      wait_time):

        # Moving
        if time.clock() < (self.startTimer + move_time) or wait_time == 0:
            xSetSpeed = xSpeed
            ySetSpeed = ySpeed
            yawSetSpeed = yawSpeed
            zSetSpeed = zSpeed

        # Waiting
        else:
            xSetSpeed = 0
            ySetSpeed = 0
            yawSetSpeed = 0
            zSetSpeed = 0
            # Resetting timer, so that drone moves again
            if time.clock() > (self.startTimer + move_time + wait_time):
                self.startTimer = time.clock()

        self.controller.SetCommand(xSetSpeed, ySetSpeed, yawSetSpeed,
                                   zSetSpeed)

        # logs info
        #self.logger.Log(
        #" altitude: " + str(self.flightInfo["altitude"]) +
        #" yawSpeed: " + str(yawSetSpeed) + " zSpeed: " + str(zSetSpeed) )

    # if there is a photo directive running, save pictures
    def SaveCachePictures(self):
        rospy.logwarn("saving cache pictures")
        if self.photoDirective != None:
            self.photoDirective.SavePhotos(None, None)
            self.photoDirective = None
        else:
            rospy.logwarn("none")

    # this is called by ROS when the node shuts down
    def ShutdownTasks(self):
        self.logger.Stop()
class CapturePhotoDirective(AbstractDroneDirective):

    # sets up this directive
    # picture path: location to save this photo
    # picturesToTake: the # of pictures to take until this directive is considred done
    # pause: time to wait before taking the next picture, in seconds
    # objectName: name of picture to be taken (will be appended to picture filename
    # angles: Number of angles that is being used for the circle
    # objectAltitude: how high off the ground the object being taken is
    def __init__(self,
                 picturePath,
                 picturesToTake=1,
                 pause=0.5,
                 objectName="",
                 angles=0,
                 objectAltitude=None,
                 startingAngle=0):

        self.pictureManager = PictureManager(picturePath)
        self.picturesToTake = picturesToTake
        self.pause = pause
        self.objectName = objectName
        self.angles = angles
        self.objectAltitude = objectAltitude
        self.startingAngle = startingAngle

        # describes the nth time this class has been called in the state machine
        self.captureRound = 1
        # describes the # of times pictures have been taken for this state call
        self.picturesTaken = 1
        self.lastTaken = time.clock()
        self.shownInitMessage = False

        # stores the images in the form of a tuple (image, filename)
        self.imageCache = []

    # given the image and navdata of the drone, returns the following in order:
    #
    # A directive status int:
    #   0 if the algorithm is still working on taking pictures
    #   1 if algorithm is finished and the specified # of pictures have been taken
    #
    # A tuple of (xspeed, yspeed, yawspeed, zspeed):
    #   indicating the next instructions to fly the drone
    #
    # An image reflecting what is being done as part of the algorithm
    def RetrieveNextInstruction(self, image, navdata):

        if self.picturesTaken == 0 and not self.shownInitMessage:
            rospy.logwarn("Taking " + str(self.picturesToTake) +
                          " pictures ...")
            self.shownInitMessage = True

        if self.picturesTaken <= self.picturesToTake:
            # contents are called if it's time to take a picture
            if (time.clock() - self.lastTaken) > self.pause:

                # creating the filename for this particular picture
                if self.angles != 0:
                    currAngle = (360 / self.angles *
                                 (self.captureRound - 1)) + self.startingAngle
                else:
                    currAngle = 0
                if self.objectAltitude == None:
                    altitude = str(navdata["altitude"][1])
                else:
                    offset = 200
                    altitude = navdata["altitude"][
                        1] + offset - self.objectAltitude
                    if altitude > 0:
                        altitude = "+" + str(altitude)
                    else:
                        altitude = str(altitude)
                pictureName = (str(currAngle) + " Degrees _ Picture " +
                               str(self.picturesTaken) + " _ " +
                               self.objectName)
                self.imageCache.append((image, pictureName))
                #pictureName = self.pictureManager.Capture(image, imageName = pictureName)
                rospy.logwarn("Took picture # " + str(self.picturesTaken) +
                              " as " + pictureName + ".png")
                self.picturesTaken += 1
                self.lastTaken = time.clock()

            directiveStatus = 0

        else:
            rospy.logwarn("****** Successfully took " +
                          str(self.picturesToTake) + " pictures ******")
            directiveStatus = 1

        return directiveStatus, (0, 0, 0, 0.0), image, (None, None), 0, 0, None

    # This method is called by the state machine when it considers this directive finished
    def Finished(self):
        rospy.logwarn("Resetting picture counter")
        self.picturesTaken = 1
        self.captureRound += 1
        self.shownInitMessage = False
        return None

    # saves all the images stored in the cache
    def SavePhotos(self, image, navdata):
        rospy.logwarn("Processing the " + str(len(self.imageCache)) +
                      " pictures taken ...")
        pictureNum = 1

        for image in self.imageCache:
            pictureName, picturePath = self.pictureManager.Capture(
                image[0], imageName=image[1])
            rospy.logwarn("Saving picture " + str(pictureNum) + "/" +
                          str(len(self.imageCache)) + " : " + pictureName)
            rospy.logwarn("@ Location: " + str(picturePath))
            pictureNum += 1

        rospy.logwarn(" ... Done")
        directiveStatus = 1
        return directiveStatus, (0, 0, 0, 0.0), image, (None, None), 0, 0, None