How to programmatically download Images from drone using the IOS DJI-SDK

1.4k views Asked by At

I've been having some issues downloaded images from my drone directly to my application. I keep getting a "System is busy, please retry later.(code:-1004)" error message when attempting to download the image, I have checked the DJI forum and other questions here on stackoverflow and have not been able to find any solution to this problem.

I've already taken a look at this question, but i'm already using the technique suggested from that answer.

Here is the function I've written to download the images:

func downloadFilesFromDrone(){

    // get current product
    guard let drone = DJISDKManager.product() else {
        Logger.logError("Product is connected but DJISDKManager.product is nil when attempting to download media")
        return
    }


    // Get camera on drone
    guard  let camera: DJICamera = drone.camera else {
        Logger.logError("Unable to detect Camera in downloadFilesFromDrone()")
        // make recursive call until we are able to detect the camera
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            Logger.logH1("Trying to detect Camera again")
            self.downloadFilesFromDrone()
        }
        return
    }

    Logger.logH1("Successfully detected the camera")

    // check if we can download images with the product
     if !camera.isMediaDownloadModeSupported() {
        Logger.logError("Product does not support media download mode")
        return
    }


    // switch camera mode to allow for media downloads
    camera.setMode( .mediaDownload, withCompletion: {(error) in
        if error != nil {
            print("\(error!.localizedDescription)")
        }
        else {

            // get the media manager from the drone to gain access to the files
            let manager = camera.mediaManager!
            manager.refreshFileList(completion: { (error) in

                if error != nil {
                    print("State: \(manager.fileListState.rawValue)")
                    print("Error refreshing list: \(error!.localizedDescription)")
                }
                else {
                    Logger.logH1("Refreshed file list")
                    print("State: \(manager.fileListState.rawValue)")


                    guard let files = manager.fileListSnapshot() else {
                        Logger.logError("No files to download")
                        return
                    }

                    Logger.logH1("There are files to download")

                    var images: [UIImage] = []

                    for file in files {

                        if file.mediaType == .JPEG {

                            print("Time created: \(file.timeCreated)")

                            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {

                                file.fetchData(withOffset: 0, update: DispatchQueue.main, update: {(_ data: Data?, _ isComplete: Bool, _ error: Error?) -> Void in

                                    if error != nil {
                                        print("State: \(manager.fileListState.rawValue)")
                                        print("Error downloading photo: \(error!)")
                                    }
                                    else {
                                        // unwrap downloaded data and create image
                                        if let data = data, let downloadedImage = UIImage(data: data) {
                                            print("Image was downloaded!")
                                            images.append( downloadedImage )
                                        }
                                    }

                                }) // end of filedata fetch

                            }

                        }

                    } // end of loop


                }
            }) // end of file-refresh block

        }

    })// end of camera setMode block

}

And here is the output of this function when testing with my drone:

*** Product Connected ***
*** Unable to detect Camera in downloadFilesFromDrone() ***
*** Firmware package version is: Unknown ***
--> Trying to detect Camera again
--> Successfully detected the camera
--> Refreshed file list
State: 0
--> There are files to download
Time created: 2017-09-01 15:17:04
Time created: 2017-09-01 15:17:16
Time created: 2017-09-01 15:17:26
Time created: 2017-09-01 15:17:36
Time created: 2017-09-01 15:19:06
State: 0
Error downloading photo: System is busy, please retry later.(code:-1004)
State: 0
Error downloading photo: System is busy, please retry later.(code:-1004)
State: 0
Error downloading photo: System is busy, please retry later.(code:-1004)
State: 0
Error downloading photo: System is busy, please retry later.(code:-1004)

Edit:

Below is the code I used to download images from the drone.

/**
 * This function downloads the N latest images from the drone and passes them to the completionhandler once all images have completed downloading
 */
func downloadImages( files: [DJIMediaFile], howMany: Int, maxErrors: Int, completion: @escaping ([UIImage]) -> Void){

    Logger.logH1("Queueing \(howMany) image(s) to be downloaded")

    func downloadNextImage( files: [DJIMediaFile], fileCount: Int, index: Int = 0, downloadedFiles: [UIImage] = [], errorCount: Int = 0) {

        // stop when we reach the end of the list
        if index == fileCount {
            completion(downloadedFiles)
            return
        }
        else {
            var imageData: Data?
            let file = files[index]

            file.fetchData(withOffset: 0, update: DispatchQueue.main, update: {(_ data: Data?, _ isComplete: Bool, _ error: Error?) -> Void in

                if let error = error {
                    Logger.logError("\(error)")

                    if errorCount < maxErrors {
                        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                            Logger.logH1("Attempting to download: \(file.fileName) again")
                            downloadNextImage(files: files, fileCount: fileCount, index: index, downloadedFiles: downloadedFiles, errorCount: errorCount + 1)
                        }

                    }
                    else {
                        Logger.logError("Too many errors downloading the images, try downloading again")
                    }


                }
                else {
                    // if image is done downloading
                    if isComplete {

                        // get full image data
                        if let imageData = imageData, let image = UIImage(data: imageData) {
                            Logger.logH1("Downloaded: \(file.fileName)")

                            // now that the image is done downloading, move onto the next image
                            downloadNextImage(files: files, fileCount: fileCount, index: (index + 1), downloadedFiles: downloadedFiles + [image], errorCount: 0)
                        }
                    }
                        // else, download the file
                    else {

                        // If image exists, append the data
                        if let _ = imageData, let data = data {
                            imageData?.append(data)
                        }
                            // initialize the image data
                        else {
                            imageData = data
                        }

                    }
                }


            }) // end of filedata fetch


        }   // end of else statement
    }

    // bounds checking
    let available = files.count
    let n = howMany > available ? available : howMany

    // grab the N latest images taken by the drone
    let filesToDownload : [DJIMediaFile] = Array ( files.suffix(n) )


    // start the recursive function
    downloadNextImage(files: filesToDownload, fileCount: filesToDownload.count)
}

And here is how to call it:

  // get current product
    guard let drone = DJISDKManager.product() else {
        Logger.logError("Product is connected but DJISDKManager.product is nil when attempting to download media")
        return
    }


    // Get camera on drone
    guard  let camera: DJICamera = drone.camera else {
        Logger.logError("Unable to detect Camera in initDownload()")
        return
    }

    Logger.logH1("Successfully detected the camera")

    // check if we can download images with the product
    if !camera.isMediaDownloadModeSupported() {
        Logger.logError("Product does not support media download mode")
        return
    }

    // switch camera mode to allow for media downloads
    camera.setMode( .mediaDownload, withCompletion: {(error) in
        if error != nil {
            print("\(error!.localizedDescription)")
        }
        else {

            // get the media manager from the drone to gain access to the files
            let manager = camera.mediaManager!
            manager.refreshFileList(completion: { (error) in

                if error != nil {
                    print("State: \(manager.fileListState.rawValue)")
                    print("Error refreshing list: \(error!.localizedDescription)")
                }
                else {
                    Logger.logH1("Refreshed file list")
                    print("State: \(manager.fileListState.rawValue)")

                    // get list of files
                    guard let files = manager.fileListSnapshot() else {
                        Logger.logError("No files to download")
                        return
                    }

                    Logger.logH1("There are files to download.. Beginning Download")
                    self.downloadImages(files: files, howMany: waypoints, maxErrors: 4, completion: { images in
                        Logger.logH1("Finished downloading: \(images.count) image(s)")
                        // do something with the images here
                    })



                }
            }) // end of file-refresh block

        }

    })// end of camera setMode block
1

There are 1 answers

4
biomiker On BEST ANSWER

You've got two problems here. Firstly, your updateBlock is inadequate because it's assuming that all the data will come in a single call. Look more carefully at the documentation for fetchData()

Note the definition of updateBlock: "Block to receive file data. It will be called multiple times and each time will return the data received since the last call."

So your updateBlock needs to do something like this:

imageData.append(data)
if isComplete {
    image = UIImage(data: imageData)
    // handle image as desired
}

The second problem is that you are requesting the asynchronous download of all the files simultaneously. You need to download only one file at a time, and only start the next one after the previous one is complete. For example:

imageData.append(data)
if isComplete {
    image = UIImage(data: imageData)
    // handle image as desired

    // then, initiate next download
    downloadFilesFromDrone()
}