簡體   English   中英

DJI SDK 航點任務 - swift, IOS

[英]DJI SDK waypoint Missions - swift, IOS

我想編寫一個系統,其中坐標可以作為航點傳遞給無人機,無人機將執行操作。 DJI API 用 OBJ-c 記錄,雖然概念相同,但我很難理解任務是如何編程的。

如果有人可以幫助我了解航路點任務的基本結構並將其傳遞給無人機,那將非常有幫助。 也許我不太了解事情,但 DJI API 似乎並不能很好地描述事情的運作方式。

我不是要求被勺子喂食,而是有見識的人可以給我一個解釋

在 github 上查看 dji 示例應用程序: https://github.com/dji-sdk/Mobile-SDK-iOS/tree/master/Sample%20Code/ObjcSampleCode/DJISdkDemo/Demo/MissionManager

//
//  WaypointMissionViewController.m
//  DJISdkDemo
//
//  Copyright © 2015 DJI. All rights reserved.
//
/**
 *  This file demonstrates the process to start a waypoint mission. In this demo,
 *  the aircraft will go to four waypoints, shoot photos and record videos.
 *  The flight speed can be controlled by calling the class method
 *  setAutoFlightSpeed:withCompletion:. In this demo, when the aircraft will
 *  change the speed right after it reaches the second point (point with index 1).
 *
 *  CAUTION: it is highly recommended to run this sample using the simulator.
 */
#import <DJISDK/DJISDK.h>
#import "DemoUtility.h"
#import "WaypointMissionViewController.h"

#define ONE_METER_OFFSET (0.00000901315)

@interface WaypointMissionViewController ()

@property (nonatomic) DJIWaypointMissionOperator *wpOperator;
@property (nonatomic) DJIWaypointMission *downloadMission;

@end

@implementation WaypointMissionViewController

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    self.wpOperator = [[DJISDKManager missionControl] waypointMissionOperator];
}

-(void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self.wpOperator removeListenerOfExecutionEvents:self];
}

/**
 *  Because waypoint mission is refactored and uses a different interface design
 *  from the other missions, we need to override the UI actions.
 */
-(void)onPrepareButtonClicked:(id)sender {
    DJIWaypointMission *wp = (DJIWaypointMission *)[self initializeMission];

    NSError *error = [self.wpOperator loadMission:wp];
    if (error) {
        ShowResult(@"Prepare Mission Failed:%@", error);
        return;
    }
    
    WeakRef(target);
    [self.wpOperator addListenerToUploadEvent:self withQueue:nil andBlock:^(DJIWaypointMissionUploadEvent * _Nonnull event) {
        WeakReturn(target);
        [target onUploadEvent:event];
    }];
    
    [self.wpOperator uploadMissionWithCompletion:^(NSError * _Nullable error) {
        WeakReturn(target);
        if (error) {
            ShowResult(@"ERROR: uploadMission:withCompletion:. %@", error.description);
        }
        else {
            ShowResult(@"SUCCESS: uploadMission:withCompletion:.");
        }
    }];
}

- (IBAction)onStartButtonClicked:(id)sender {
    
    WeakRef(target);
    [self.wpOperator addListenerToExecutionEvent:self withQueue:nil andBlock:^(DJIWaypointMissionExecutionEvent * _Nonnull event) {
        [target showWaypointMissionProgress:event];
    }];
    
    [self.wpOperator startMissionWithCompletion:^(NSError * _Nullable error) {
        if (error) {
            ShowResult(@"ERROR: startMissionWithCompletion:. %@", error.description);
        }
        else {
            ShowResult(@"SUCCESS: startMissionWithCompletion:. ");
        }
        [self missionDidStart:error];
    }];
}

-(void)onStopButtonClicked:(id)sender {
    WeakRef(target);
    [self.wpOperator stopMissionWithCompletion:^(NSError * _Nullable error) {
        WeakReturn(target);
        if (error) {
            ShowResult(@"ERROR: stopMissionExecutionWithCompletion:. %@", error.description);
        }
        else {
            ShowResult(@"SUCCESS: stopMissionExecutionWithCompletion:. ");
        }
        [target missionDidStop:error];
    }];
}

-(void)onDownloadButtonClicked:(id)sender {
    
    self.downloadMission = nil;
    WeakRef(target);
    [self.wpOperator addListenerToDownloadEvent:self
                                      withQueue:nil
                                       andBlock:^(DJIWaypointMissionDownloadEvent * _Nonnull event)
     {

         if (event.progress.downloadedWaypointIndex == event.progress.totalWaypointCount) {
             ShowResult(@"SUCCESS: the waypoint mission is downloaded. ");
             target.downloadMission = target.wpOperator.loadedMission;
             [target.wpOperator removeListenerOfDownloadEvents:target];
             [target.progressBar setHidden:YES];
             [target mission:target.downloadMission didDownload:event.error];
         }
         else if (event.error) {
             ShowResult(@"Download Mission Failed:%@", event.error);
             [target.progressBar setHidden:YES];
             [target mission:target.downloadMission didDownload:event.error];
             [target.wpOperator removeListenerOfDownloadEvents:target];
         } else {
             [target.progressBar setHidden:NO];
             float progress = ((float)event.progress.downloadedWaypointIndex + 1) / (float)event.progress.totalWaypointCount;
             NSLog(@"Download Progress:%d%%", (int)(progress*100));
             [target.progressBar setProgress:progress];
         }
     }];
    
    [self.wpOperator downloadMissionWithCompletion:^(NSError * _Nullable error) {
        WeakReturn(target);
        if (error) {
            ShowResult(@"ERROR: downloadMissionWithCompletion:withCompletion:. %@", error.description);
        }
        else {
            ShowResult(@"SUCCESS: downloadMissionWithCompletion:withCompletion:.");
        }
    }];
}

-(void)onPauseButtonClicked:(id)sender {
    [self missionWillPause];
    [self.wpOperator pauseMissionWithCompletion:^(NSError * _Nullable error) {
        if (error) {
            ShowResult(@"ERROR: pauseMissionWithCompletion:. %@", error.description);
        }
        else {
            ShowResult(@"SUCCESS: pauseMissionWithCompletion:. ");
        }
    }];
}

-(void)onResumeButtonClicked:(id)sender {
    WeakRef(target);
    [self.wpOperator resumeMissionWithCompletion:^(NSError * _Nullable error) {
        WeakReturn(target);
        if (error) {
            ShowResult(@"ERROR: resumeMissionWithCompletion:. %@", error.description);
        }
        else {
            ShowResult(@"SUCCESS: resumeMissionWithCompletion:. ");
        }
        [target missionDidResume:error];
    }];
}

/**
 *  Prepare the waypoint mission. The basic workflow is:
 *  1. Create an instance of DJIWaypointMission.
 *  2. Create coordinates.
 *  3. Use the coordinate to create an instance of DJIWaypoint.
 *  4. Add actions for each waypoint.
 *  5. Add the waypoints into the mission.
 */
-(DJIMission*) initializeMission {
    // Step 1: create mission
    DJIMutableWaypointMission* mission = [[DJIMutableWaypointMission alloc] init];
    mission.maxFlightSpeed = 15.0;
    mission.autoFlightSpeed = 4.0;


    // Step 2: prepare coordinates
    CLLocationCoordinate2D northPoint;
    CLLocationCoordinate2D eastPoint;
    CLLocationCoordinate2D southPoint;
    CLLocationCoordinate2D westPoint;
    northPoint = CLLocationCoordinate2DMake(self.homeLocation.latitude + 10 * ONE_METER_OFFSET, self.homeLocation.longitude);
    eastPoint = CLLocationCoordinate2DMake(self.homeLocation.latitude, self.homeLocation.longitude + 10 * ONE_METER_OFFSET);
    southPoint = CLLocationCoordinate2DMake(self.homeLocation.latitude - 10 * ONE_METER_OFFSET, self.homeLocation.longitude);
    westPoint = CLLocationCoordinate2DMake(self.homeLocation.latitude, self.homeLocation.longitude - 10 * ONE_METER_OFFSET);

    // Step 3: create waypoints
    DJIWaypoint* northWP = [[DJIWaypoint alloc] initWithCoordinate:northPoint];
    northWP.altitude = 10.0;
    DJIWaypoint* eastWP = [[DJIWaypoint alloc] initWithCoordinate:eastPoint];
    eastWP.altitude = 20.0;
    DJIWaypoint* southWP = [[DJIWaypoint alloc] initWithCoordinate:southPoint];
    southWP.altitude = 30.0;
    DJIWaypoint* westWP = [[DJIWaypoint alloc] initWithCoordinate:westPoint];
    westWP.altitude = 40.0;
    
    // Step 4: add actions
    [northWP addAction:[[DJIWaypointAction alloc] initWithActionType:DJIWaypointActionTypeRotateGimbalPitch param:-60]];
    [northWP addAction:[[DJIWaypointAction alloc] initWithActionType:DJIWaypointActionTypeShootPhoto param:0]];
    [eastWP addAction:[[DJIWaypointAction alloc] initWithActionType:DJIWaypointActionTypeShootPhoto param:0]];
    [southWP addAction:[[DJIWaypointAction alloc] initWithActionType:DJIWaypointActionTypeRotateAircraft param:60]];
    [southWP addAction:[[DJIWaypointAction alloc] initWithActionType:DJIWaypointActionTypeStartRecord param:0]];
    [westWP addAction:[[DJIWaypointAction alloc] initWithActionType:DJIWaypointActionTypeStopRecord param:0]];

    // Step 5: add waypoints into the mission
    [mission addWaypoint:northWP];
    [mission addWaypoint:eastWP];
    [mission addWaypoint:southWP];
    [mission addWaypoint:westWP]; 
    
    return mission; 
}


- (void)onUploadEvent:(DJIWaypointMissionUploadEvent *) event
{
    
    if (event.currentState == DJIWaypointMissionStateReadyToExecute) {
        ShowResult(@"SUCCESS: the whole waypoint mission is uploaded.");
        [self.progressBar setHidden:YES];
        [self.wpOperator removeListenerOfUploadEvents:self];
    }
    else if (event.error) {
        ShowResult(@"ERROR: waypoint mission uploading failed. %@", event.error.description);
        [self.progressBar setHidden:YES];
        [self.wpOperator removeListenerOfUploadEvents:self];
    }
    else if (event.currentState == DJIWaypointMissionStateReadyToUpload ||
             event.currentState == DJIWaypointMissionStateNotSupported ||
             event.currentState == DJIWaypointMissionStateDisconnected) {
        ShowResult(@"ERROR: waypoint mission uploading failed. %@", event.error.description);
        [self.progressBar setHidden:YES];
        [self.wpOperator removeListenerOfUploadEvents:self];
    } else if (event.currentState == DJIWaypointMissionStateUploading) {
        [self.progressBar setHidden:NO];
        DJIWaypointUploadProgress *progress = event.progress;
        float progressInPercent = progress.uploadedWaypointIndex / progress.totalWaypointCount;
        [self.progressBar setProgress:progressInPercent];
    }
}

-(void) showWaypointMissionProgress:(DJIWaypointMissionExecutionEvent *)event {
    NSMutableString* statusStr = [NSMutableString new];
    [statusStr appendFormat:@"previousState:%@\n", [[self class] descriptionForMissionState:event.previousState]];
    [statusStr appendFormat:@"currentState:%@\n", [[self class] descriptionForMissionState:event.currentState]];
    
    [statusStr appendFormat:@"Target Waypoint Index: %zd\n", (long)event.progress.targetWaypointIndex];
    [statusStr appendString:[NSString stringWithFormat:@"Is Waypoint Reached: %@\n",
                             event.progress.isWaypointReached ? @"YES" : @"NO"]];
    [statusStr appendString:[NSString stringWithFormat:@"Execute State: %@\n", [[self class] descriptionForExecuteState:event.progress.execState]]];
    if (event.error) {
        [statusStr appendString:[NSString stringWithFormat:@"Execute Error: %@", event.error.description]];
        [self.wpOperator removeListenerOfExecutionEvents:self];
    }
    
    [self.statusLabel setText:statusStr];
}

/**
 *  Display the information of the mission if it is downloaded successfully.
 */
-(void)mission:(DJIMission *)mission didDownload:(NSError *)error {
    if (error) return;
    
    if ([mission isKindOfClass:[DJIWaypointMission class]]) {
        // Display information of waypoint mission.
        [self showWaypointMission:(DJIWaypointMission*)mission];
    }
}

-(void) showWaypointMission:(DJIWaypointMission*)wpMission {
    NSMutableString* missionInfo = [NSMutableString stringWithString:@"The waypoint mission is downloaded successfully: \n"];
    [missionInfo appendString:[NSString stringWithFormat:@"RepeatTimes: %zd\n", wpMission.repeatTimes]];
    [missionInfo appendString:[NSString stringWithFormat:@"HeadingMode: %u\n", (unsigned int)wpMission.headingMode]];
    [missionInfo appendString:[NSString stringWithFormat:@"FinishedAction: %u\n", (unsigned int)wpMission.finishedAction]];
    [missionInfo appendString:[NSString stringWithFormat:@"FlightPathMode: %u\n", (unsigned int)wpMission.flightPathMode]];
    [missionInfo appendString:[NSString stringWithFormat:@"MaxFlightSpeed: %f\n", wpMission.maxFlightSpeed]];
    [missionInfo appendString:[NSString stringWithFormat:@"AutoFlightSpeed: %f\n", wpMission.autoFlightSpeed]];
    [missionInfo appendString:[NSString stringWithFormat:@"There are %zd waypoint(s). ", wpMission.waypointCount]];
    [self.statusLabel setText:missionInfo];
}

+(NSString *)descriptionForMissionState:(DJIWaypointMissionState)state {
    switch (state) {
        case DJIWaypointMissionStateUnknown:
            return @"Unknown";
        case DJIWaypointMissionStateExecuting:
            return @"Executing";
        case DJIWaypointMissionStateUploading:
            return @"Uploading";
        case DJIWaypointMissionStateRecovering:
            return @"Recovering";
        case DJIWaypointMissionStateDisconnected:
            return @"Disconnected";
        case DJIWaypointMissionStateNotSupported:
            return @"NotSupported";
        case DJIWaypointMissionStateReadyToUpload:
            return @"ReadyToUpload";
        case DJIWaypointMissionStateReadyToExecute:
            return @"ReadyToExecute";
        case DJIWaypointMissionStateExecutionPaused:
            return @"ExecutionPaused";
    }
    
    return @"Unknown";
}

+(NSString *)descriptionForExecuteState:(DJIWaypointMissionExecuteState)state {
    switch (state) {
        case DJIWaypointMissionExecuteStateInitializing:
            return @"Initializing";
            break;
        case DJIWaypointMissionExecuteStateMoving:
            return @"Moving";
        case DJIWaypointMissionExecuteStatePaused:
            return @"Paused";
        case DJIWaypointMissionExecuteStateBeginAction:
            return @"BeginAction";
        case DJIWaypointMissionExecuteStateDoingAction:
            return @"Doing Action";
        case DJIWaypointMissionExecuteStateFinishedAction:
            return @"Finished Action";
        case DJIWaypointMissionExecuteStateCurveModeMoving:
            return @"CurveModeMoving";
        case DJIWaypointMissionExecuteStateCurveModeTurning:
            return @"CurveModeTurning";
        case DJIWaypointMissionExecuteStateReturnToFirstWaypoint:
            return @"Return To first Point";
        default:
            break;
    }
    return @"Unknown";
}

@end

這就是我用 swift 編寫航點 function 的方式,您可以閱讀下面的代碼注釋以了解任務規范!

/// Build a waypoint function that allows the drone to move between points according to its altitude, longitude, and latitude
    func waypointMission() -> DJIWaypointMission? {
        /// Define a new object class for the waypoint mission
        let mission = DJIMutableWaypointMission()
        
        mission.maxFlightSpeed = 15 
        mission.autoFlightSpeed = 8
        mission.finishedAction = .noAction
        mission.headingMode = .usingInitialDirection
        mission.flightPathMode = .normal
        mission.rotateGimbalPitch = false /// Change this to True if you want the camera gimbal pitch to move between waypoints
        mission.exitMissionOnRCSignalLost = true
        mission.gotoFirstWaypointMode = .pointToPoint
        mission.repeatTimes = 1
        
        /// Keep listening to the drone location included in latitude and longitude
        guard let droneLocationKey = DJIFlightControllerKey(param: DJIFlightControllerParamAircraftLocation) else {
            return nil
        }
        guard let droneLocationValue = DJISDKManager.keyManager()?.getValueFor(droneLocationKey) else {
            return nil
        }
        let droneLocation = droneLocationValue.value as! CLLocation
        let droneCoordinates = droneLocation.coordinate
        /// Check if the returned coordinate value is valid or not
        if !CLLocationCoordinate2DIsValid(droneCoordinates) {
            return nil
        }
        mission.pointOfInterest = droneCoordinates
       
        let loc1 = CLLocationCoordinate2DMake(droneCoordinates.latitude, droneCoordinates.longitude)
        let waypoint1 = DJIWaypoint(coordinate: loc1)
        
        waypoint1.altitude = 2.0 /// The altitude which the drone flies to as the first point and should be of type float
        waypoint1.heading = 0 /// This is between [-180, 180] degrees, where the drone moves when reaching a waypoint. 0 means don't change the drone's heading
        waypoint1.actionRepeatTimes = 1 /// Repeat this mission just for one time
        waypoint1.actionTimeoutInSeconds = 60
        // waypoint1.cornerRadiusInMeters = 5
        waypoint1.turnMode = .clockwise /// When the drones changing its heading. It moves clockwise
        waypoint1.gimbalPitch = 0 /// This is between [-90, 0] degrees, if you want to change this value, then change rotateGimbalPitch to True. The drone gimbal will move by the value when the drone reaches its waypoint
        waypoint1.speed = 0.5 /// Note that this value does not make the drone move with speed 0.5 m/s because this is an error from the firmware and can't be fixed. However, we need to trigger it to be able to change the next one
        
        let loc2 = CLLocationCoordinate2DMake(droneCoordinates.latitude, droneCoordinates.longitude)
        let waypoint2 = DJIWaypoint(coordinate: loc2)
        waypoint2.altitude = 15.0 /// should be of type float
        waypoint2.heading = 0
        waypoint2.actionRepeatTimes = 1
        waypoint2.actionTimeoutInSeconds = 60
        //waypoint2.cornerRadiusInMeters = 5
        waypoint2.turnMode = .clockwise
        waypoint2.gimbalPitch = 0
        /// Chnage the velocity of the drone while moving to this waypoint
        waypoint2.speed = 0.5
        
        mission.add(waypoint1)
        mission.add(waypoint2)
        
        return DJIWaypointMission(mission: mission)
    }

現在調用之前的function時,需要傳遞給任務的時間線,這里是一個完整時間線任務的示例流程

/// Set up the drone misison and strart its timeline
    func goUpVerticallyMission() {
        /// Check if the drone is connected
        let product = DJISDKManager.product()
        
        if (product?.model) != nil {
            
            /// This is the array that holds all the timline elements to be executed later in order
            var elements = [DJIMissionControlTimelineElement]()
            
            /// Reset Gimbal Position
            let attitude = DJIGimbalAttitude(pitch: 0.0, roll: 0.0, yaw: 0.0)
            let pitchAction: DJIGimbalAttitudeAction = DJIGimbalAttitudeAction(attitude: attitude)!
            elements.append(pitchAction) // task number 0
            
            
            let takeOff = DJITakeOffAction()
            elements.append(takeOff) // task number 1

            /// Set up and start a new waypoint mission
            var mission: DJIWaypointMission?
            guard let result = self.waypointMission() else {return}
            mission = result
            elements.append(mission!) // task number 2
            
            /// After the waypoint mission finishes stop recording the video
            let stopVideoAction = DJIRecordVideoAction(stopRecordVideo: ())
            elements.append(stopVideoAction!) // task number 3
            
            /// This is landing action after finishing the  task
            // let landAction = DJILandAction()
            // elements.append(landAction)
            
            /// This is the go home and landing action in which the drone goes back to its starting point when the first time started the mission
            let goHomeLandingAction = DJIGoHomeAction()
            elements.append(goHomeLandingAction) /// task number 4
            
            /// Check if there is any error while appending the timeline mission to the array.
            let error = DJISDKManager.missionControl()?.scheduleElements(elements)
            if error != nil {
                print("Error detected with the mission")
            } else {
                /// If there is no error then start the mission
                DispatchQueue.main.asyncAfter(deadline: .now()) {
                    DJISDKManager.missionControl()?.startTimeline()
                }
            }
        }
    }

如果您有任何誤解或在其他方面需要任何幫助,請告訴我!!!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM