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
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:
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: