Flight plans on Anafi not working. incorrectFlightPlanFile

Hi there,

We are trying to implement flight plans in our app. More specifically we want to translate our UI into commands to be sent to the drone, then create a mavlink file, store it locally and then send it to the drone.

The connection seems to be working fine and the mavlink file is being uploaded to the drone correctly. The problem comes when we try to execute the file, it shows the incorrectFlightPlanFile error.

I upload our mavlink file as well as the portion of the code in charge of creating it (see the mavlink file attached inside the zip file). Just to provide a little bit of context the onOpened method in the class below is called when the drone is connected and the onClosed when the drone disconnects. If you can help us figure out this issue we will really appreciate it. Thanks in advance.

class ParrotGridFlightProvider: DroneSessionManagerDelegate {
    
    static let shared = ParrotGridFlightProvider()
    
    private var session: DroneSession?
    
    private var pilotingItf: Ref<FlightPlanPilotingItf>?
    
    private var missionStarted = false
    
    func onOpened(session: DroneSession) {
        self.session = session
        self.pilotingItf = (session as! ParrotDroneSession).adapter.drone.getPilotingItf(PilotingItfs.flightPlan) { [weak self] value in
            let result = self?.activateMission()
            print("Matias Test", "Drone can fly: \(String(describing: result))")
            print("Matias Test", "Drone state: \(String(describing: self?.pilotingItf?.value?.state))" )
            print("Matias Test", "Drone error: \(String(describing: self?.pilotingItf?.value?.latestActivationError))")
            print("Matias Test", "Drone is paused: \(String(describing: self?.pilotingItf?.value?.isPaused))")
        }
    }
    
    func onClosed(session: DroneSession) {
        self.session = nil
        self.pilotingItf = nil
    }
    
    public func startMission(commands: [ParrotGridFlightCommand]) {
        let filePath = self.writeFile(commands: commands)
        self.pilotingItf?.value?.uploadFlightPlan(filepath: filePath)
    }
    
    private func activateMission() -> Bool {
        if let pilotingItf = pilotingItf?.value {
            if pilotingItf.state == .idle && !missionStarted {
                missionStarted = true
                return pilotingItf.activate(restart: true)
            }
        }
        
        return missionStarted ? true : false
    }
    
    private func getDirectory() -> URL {
        let fileManager = FileManager.default
        let documentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let flightPlanFolderPath = documentPath.appendingPathComponent("flightPlans")
        
        try? fileManager.createDirectory(at: flightPlanFolderPath, withIntermediateDirectories: false, attributes: nil)
        
        return flightPlanFolderPath
    }
    
    private func writeFile(commands: [ParrotGridFlightCommand]) -> String {
        let path = getDirectory().appendingPathComponent("\(Date().toServerDate()).mavlink").path
        let fileManager = FileManager.default
        if !fileManager.fileExists(atPath: path) {
            do {
                try "".write(toFile: path, atomically: true, encoding: .utf8)
            }
            catch let error as NSError {
                NSLog("Unable to create directory \(error.debugDescription)")
            }
        }
        
        if let fileHandle = FileHandle(forWritingAtPath: path) {
            fileHandle.seekToEndOfFile()
            let header = "QGC WPL 120"
            fileHandle.write(header.data(using: .utf8)!)
            let newLine = "\n"
            fileHandle.write(newLine.data(using: .utf8)!)
            for command in commands {
                fileHandle.seekToEndOfFile()
                let value = "\(command.index) \(command.currentWP) \(command.coordFrame) \(command.command.rawValue) \(command.param1) \(command.param2) \(command.param3) \(command.param4) \(command.longitude) \(command.latitude) \(command.altitude) \(command.autoContinue ? 1 : 0)"
                fileHandle.write(value.data(using: .utf8)!)
                let newLine = "\n"
                fileHandle.write(newLine.data(using:    .utf8)!)
            }
            fileHandle.closeFile()
        }
        
        return path
    }
}

2021-06-07T13:55:08.022.mavlink.zip (348 Bytes)

Do you follow our standard for flightplan file ?
https://developer.parrot.com/docs/mavlink-flightplan/

Hi Jerome,

Yes I did! I even tried with tabs and spaces and different ways but none seem to be accepted. You can find an example attached in the first message. I even tried the GroundSdkDemo with the same file but the experience there is a little bit different. There it stays stuck in “uploading” state.

I also tried with this one and it’s not working.
2021-06-07T21:13:01.187.mavlink.zip (324 Bytes)

The spaces between the numbers/fields above are <tab> (i.e. \t in most programming languages).

Yes, as I mentioned I also tried with tabs too but doesn’t seem to work. Please see attach this flight plan using tabs (same error). I also based the file creation on the Android Ground SDK demo but doesn’t seem to work. File uploads correctly but when trying to execute it I get the incorrectFlightPlanFile error. If you can send me a correct mavlink file for taking off and landing so I can compare with mine I would really appreciate it.

Thanks in advance,

Matias
Tech Lead Working for Measure

flightplan.mavlink.zip (310 Bytes)

Can you also try to use a .txt extension file ?

I tried both .json and .txt and none of them worked.

Here is the swift code just in case:

//
//  ParrotGridFlightProvider.swift
//  mfa
//
//  Created by Matias Di Russo on 24/5/21.
//  Copyright © 2021 Measure UAS, Inc. All rights reserved.
//

import Foundation
import DronelinkParrot
import DronelinkCore
import GroundSdk

class ParrotGridFlightProvider: DroneSessionManagerDelegate {
    
    static let shared = ParrotGridFlightProvider()
    private static let AUTO_CONTINUE = 1
    private static let CURRENT_WAYPOINT = 0
    private static let FRAME = 3
    
    private var session: DroneSession?
    
    private var pilotingItf: Ref<FlightPlanPilotingItf>?
    
    func onOpened(session: DroneSession) {
        self.session = session
        self.pilotingItf = (session as! ParrotDroneSession).adapter.drone.getPilotingItf(PilotingItfs.flightPlan) { [weak self] value in
            print("Matias Test", "Drone state: \(String(describing: self?.pilotingItf?.value?.state))" )
            print("Matias Test", "Unavailability reasons: \(String(describing: self?.pilotingItf?.value?.unavailabilityReasons))")
            print("Matias Test", "Drone error: \(String(describing: self?.pilotingItf?.value?.latestActivationError))")
            print("Matias Test", "Drone is paused: \(String(describing: self?.pilotingItf?.value?.isPaused))")
        }
    }
    
    func onClosed(session: DroneSession) {
        self.session = nil
        self.pilotingItf = nil
    }
    
    public func uploadMission(commands: [ParrotGridFlightCommand]) {
        let filePath = self.writeFile(commands: commands)
        self.pilotingItf?.value?.uploadFlightPlan(filepath: filePath)
    }
    
    func startMission() -> Bool {
        if let pilotingItf = pilotingItf?.value {
            if pilotingItf.state == .active {
                return pilotingItf.deactivate()
            } else if pilotingItf.state == .idle {
                return pilotingItf.activate(restart: false)
            }
        }
        
        return false
    }
    
    private func getDirectory() -> URL {
        let fileManager = FileManager.default
        let documentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let flightPlanFolderPath = documentPath.appendingPathComponent("flightPlans")
        
        try? fileManager.createDirectory(at: flightPlanFolderPath, withIntermediateDirectories: false, attributes: nil)
        
        return flightPlanFolderPath
    }
    
    private func writeFile(commands: [ParrotGridFlightCommand]) -> String {
        let path = getDirectory().appendingPathComponent("flightplan.txt").path
        do {
            try "".write(toFile: path, atomically: true, encoding: .utf8)
        }
        catch let error as NSError {
            NSLog("Unable to create directory \(error.debugDescription)")
        }
        
        if let fileHandle = FileHandle(forWritingAtPath: path) {
            let header = "QGC WPL 120\n"
            fileHandle.write(header.data(using: .utf8)!)
            var index = 0
            for command in commands {
                let value = "\(index)\t\(ParrotGridFlightProvider.CURRENT_WAYPOINT)\t\(ParrotGridFlightProvider.FRAME)\t\(command.command.rawValue)\t\(String(format: "%.6f", command.param1))\t\(String(format: "%.6f", command.param2))\t\(String(format: "%.6f", command.param3))\t\(String(format: "%.6f", command.param4))\t\(String(format: "%.6f", command.latitude))\t\(String(format: "%.6f", command.longitude))\t\(String(format: "%.6f", command.altitude))\t\(ParrotGridFlightProvider.AUTO_CONTINUE)\n"
                fileHandle.write(value.data(using: .utf8)!)
                index += 1
            }
            fileHandle.closeFile()
        }
        
        return path
    }
}

This is how we manage a MAVLink file in the SDK :

You can also see a complete upload/activate implementation in GroundSDKDemo app

Hi Jerome,

Thanks for the example, I based my development on it. I even copied/pasted the same logic but I’m still getting a incorrectFlightPlanFile exception.

I attach the code and both the .txt and .mavlink files generated
files.zip (822 Bytes)
in case you want to check and can point me where the issue might be.

//
//  ParrotGridFlightProvider.swift
//  mfa
//
//  Created by Matias Di Russo on 24/5/21.
//  Copyright © 2021 Measure UAS, Inc. All rights reserved.
//

import Foundation
import DronelinkParrot
import DronelinkCore
import GroundSdk

class ParrotGridFlightProvider: DroneSessionManagerDelegate {
    
    static let shared = ParrotGridFlightProvider()
    
    private var session: DroneSession?
    
    private var pilotingItf: Ref<FlightPlanPilotingItf>?
    
    func onOpened(session: DroneSession) {
        self.session = session
        self.pilotingItf = (session as! ParrotDroneSession).adapter.drone.getPilotingItf(PilotingItfs.flightPlan) { [weak self] value in
            print("Matias Test", "Drone state: \(String(describing: self?.pilotingItf?.value?.state))" )
            print("Matias Test", "Unavailability reasons: \(String(describing: self?.pilotingItf?.value?.unavailabilityReasons))")
            print("Matias Test", "Drone error: \(String(describing: self?.pilotingItf?.value?.latestActivationError))")
            print("Matias Test", "Drone is paused: \(String(describing: self?.pilotingItf?.value?.isPaused))")
        }
    }
    
    func onClosed(session: DroneSession) {
        self.session = nil
        self.pilotingItf = nil
    }
    
    public func uploadMission(commands: [ParrotGridFlightCommand]) {
        let filePath = self.writeFile(commands: commands)
        self.pilotingItf?.value?.uploadFlightPlan(filepath: filePath)
        print("Matias Test", filePath)
    }
    
    func startMission() -> Bool {
        if let pilotingItf = pilotingItf?.value {
            if pilotingItf.state == .active {
                return pilotingItf.deactivate()
            } else if pilotingItf.state == .idle {
                return pilotingItf.activate(restart: false)
            }
        }
        
        return false
    }
    
    private func getDirectory() -> URL {
        let fileManager = FileManager.default
        let documentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let flightPlanFolderPath = documentPath.appendingPathComponent("flightPlans")
        
        try? fileManager.createDirectory(at: flightPlanFolderPath, withIntermediateDirectories: false, attributes: nil)
        
        return flightPlanFolderPath
    }
    
    private func writeFile(commands: [ParrotGridFlightCommand]) -> String {
        let filepath = getDirectory().appendingPathComponent("flightplan.mavlink").path
        do {
            // Writing header creates the file and clears content if needed
            try "QGC WPL 120\n".write(toFile: filepath, atomically: false, encoding: .utf8)

            if let fileHandle = FileHandle(forWritingAtPath: filepath) {
                fileHandle.seekToEndOfFile()
                for (index, command) in commands.enumerated() {
                    command.write(fileHandle: fileHandle, index: index)
                }
                fileHandle.closeFile()
            }
        } catch {
            print("ParrotGridFlightProvider", "Could not generate MAVLink file: \(error)")
        }
        return filepath
    }
}

and this is the command class which is almost identical to the ground sdk demo:

//
//  ParrotGridFlightCommand.swift
//  mfa
//
//  Created by Matias Di Russo on 25/5/21.
//  Copyright © 2021 Measure UAS, Inc. All rights reserved.
//

import Foundation
struct ParrotGridFlightCommand {
    private static let autoContinue = 1
    private static let currentWaypoint = 0
    private static let frame = 3
    
    let command: ParrotCommand
    let param1: Double
    let param2: Double
    let param3: Double
    let param4: Double
    let latitude: Double
    let longitude: Double
    let altitude: Double
    
    func write(fileHandle: FileHandle, index: Int) {
        let line = String(format: "%d\t%d\t%d\t%d\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%d\n",
                          index, ParrotGridFlightCommand.currentWaypoint, ParrotGridFlightCommand.frame, command.rawValue, param1, param2, param3, param4, latitude, longitude, altitude, ParrotGridFlightCommand.autoContinue)
        if let data = line.data(using: .utf8) {
            fileHandle.write(data)
        }
    }
}

enum ParrotCommand: Int {
    case NAV_WAYPOINT = 16, NAV_LAND = 21, NAV_TAKEOFF = 22, NAV_DELAY = 93, CONDITION_DELAY = 112, DO_CHANGE_SPEED = 178, DO_SET_ROI = 201,
         DO_MOUNT_CONTROL = 205, VIDEO_START_CAPTURE = 2500, VIDEO_STOP_CAPTURE = 2501, PANORAMA_CREATE = 2800, IMAGE_START_CAPTURE = 2000,
         IMAGE_STOP_CAPTURE = 2001, NAV_RETURN_TO_LAUNCH = 20, SET_VIEW_MODE = 50000, SET_STILL_CAPTURE_MODE = 50001
}

Honestly I am not sure what else to try so if you or any of the devs can help me find the issue I would really appreciate it.

Thanks in advance.

Cheers,

Matias
Tech Lead working for Measure

Here is a working file from our test plan
mavlink_OK.txt.zip (742 Bytes)

I think the content of your file might be wrong, as I tried to replace the content of mine with yours and triggered the error.

I was able to fix it. Apparently the issue lies with the first command being a take off. In your example you don’t do a take off but rather go to the waypoint directly. I tried that with mine and worked. Is it not necessary? If that’s so I would recommend updating the example in the documentation. Thank you very much for all your help @Jerome

2 Likes

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.