How to install and use a Flight Mission#

Description#

A flight mission can be in the following states:

  • UNAVAILABLE: mission is installed but not available. Possible reasons are:

    • BROKEN: the mission will never be able to load , e.g. because firmware version is not supported.

    • LOAD_FAILED: the last load failed, e.g. because of an exception in python code.

  • UNLOADED: mission is not loaded.

  • IDLE: mission is loaded and can be activated.

  • ACTIVE: mission is active.

../_images/8-mission_state.png

The flight mission is managed from the mission UI that is running on the controller. It can either be a smartphone application using GroundSDK (ex: OpenFlight) or an PC application using Olympe/framework.

The mission UI can list, install and exchange messages with the flight mission.

The mission UI is optional, a flight mission can be installed and setup to replace the default mission.

Listing and installing flight missions from mission UI#

From GroundSDK: MissionUpdater

From Olympe:

Todo

add link when available

Start a flight mission from mission UI#

Once installed, a flight mission shall first be loaded, then activated. Several flight missions can be loaded simultaneously but only one can be active. Activating a new flight mission automatically deactivate the previous one.

From GroundSDK: MissionManager

From Olympe: MissionManager

Exchanging messages between mission and mission UI#

You can define custom messages between your mission UI and your flight mission. Messages are written using Protobuf 3.

You need to comply with the following conditions for your custom protobuf message file: * A package declaration with “<your_company>.missions.” + mission name + “.airsdk.messages”

  • Two messages called Command and Event, each containing an union of all possible command or event using a oneof tag with the name id.

  • The following properties need to be set:

    • java_package: package name in the form “com.your_company.drone.missions.” + mission name + “.airsdk

    • java_outer_classname: mission name

We also recommend to follow those rules for consistency along all protobuf messages:

  • Enumerations should be put in their own separate files, use the name Enum and have a package name corresponding to the current service, followed by the enumeration name (ex: MyPackage.MyEnum). If this is not possible, add the enumeration name in the values (ex: enumeration MyEnum with value MY_ENUM_VALUE_ONE)

  • Message and Package names should be in snake_case

  • Type name should be in CamelCase

Minimal example:

syntax = "proto3";

package your_company.missions.mymission.airsdk.messages;

option java_package = "com.your_company.drone.missions.mymission.airsdk";
option java_outer_classname = "MyMission";
option (olympe_package) = "your_company.missions.mymission.airsdk";

import 'google/protobuf/empty.proto';

message Command {
    oneof id {
        google.protobuf.Empty my_empty_command = 1;
    }
}

message Event {
    oneof id {
        uint32 my_event = 1;
    }
}

On the drone side: Use the mission_environment given in the Mission constructor.

  • make_airsdk_service_pair(package): Create a communication channel with the Mission UI given a protobuf package.

From the returned service object, use the following methods to send and receive messages.

  • send(message): Send the given message to the mission UI.

  • observe(…): Register a callback for message received from mission UI.

import fsup.services.events as events
import your_company.missions.mymission.airsdk.messages_pb2 as messages_pb

class Mission(object):
    def __init__(self, mission_environment):
        # Setup service pair with protobuf description
        self.ext_ui_msgs = self.env.make_airsdk_service_pair(messages_pb)

        # Register for incoming messages (commands)
        self.ext_ui_msgs_observer = self.ext_ui_msgs.cmd.observe_messages({
            self.ext_ui_msgs.cmd.idx.my_empty_command: self._on_msg_my_empty_command
        })

        # Send a message (event)
        self.ext_ui_msgs.evt.sender.my_event(42)

    def _on_msg_my_empty_command(self, msg):
        # 'msg' is directly the arguments of 'my_empty_command'
        # (google.protobuf.Empty in this case)
        pass

On the mobile application side: Use the MissionManager peripheral from Ground SDK.

Send the given message to the drone:

  • MissionManager - func send(message: MissionMessage)

    • message.uid: UID of the destination mission.

    • message.packageName: Package name of the protobuf message.

    • message.messageNumber: Number of the message (the value of the id oneof of the message).

    • message.payload: Message payload serialized from the protobuf message.

let missionUid = "MyMission"

/// Send message to my mission
func sendMessage(drone: Drone) {
  /// Get mission manager
  let missionManager = drone.getPeripheral(Peripherals.missionManager) {  [unowned self] missionManager in
    guard let missionManager = missionManager else {
      return
    }
    // Set package name of my mission to mission manager.
    // Mission manager will then be able to send messages and received events
    missionManager.packageNames = [missionUid]
    // Create mission command
    var command = Your_Company_Missions_Mymissions_Airsdk_Messages_Command()
    // Set command id
    command.id = .myEmptyCommand(Google_Protobuf_Empty())
    // Create paylaod
    guard let payload = try? command.serializedData() else {
      return
    }
    // Create mission message
    let message = Message(messageNumber: 1, payload: payload)
    // Send message
    missionManager.send(message: message)
  }
}

// Message class
class Message: MissionMessage {
  var uid: String = "MyMission"
  var messageNumber: UInt
  var packageName: String = "your_company.missions.mymission.airsdk.messages"
  var payload: Data

  init(messageNumber: UInt, payload: Data) {
    self.messageNumber = messageNumber
    self.payload = payload
  }
}

Read the latest message sent by the drone:

  • MissionManager - var latestMessage: MissionMessage

    • latestMessage.uid: UID of the destination mission.

    • latestMessage.packageName: Package name of the protobuf message.

    • latestMessage.messageNumber: Number of the message (the value of the id oneof of the message).

    • latestMessage.payload: Message payload serialized from the protobuf message.

var missionManager: Ref<MissionManager>?

/// Observer for my mission
func observeMission(drone: Drone) {
  // Get mission manager
  missionManager = drone.getPeripheral(Peripherals.missionManager) {  [unowned self] missionManager in
    // Get missions
    guard let missions = missionManager?.missions else {
      return
    }
    // Test if my mission is inside this array.
    guard missions.keys.contains(missionUid) else {
      return
    }
    // Get the latest message
    guard let message = missionManager?.latestMessage else {
      return
    }
    let uid = message.uid
    let messageNumber = message.messageNumber
    let packageName = message.packageName
    let payload = message.payload

    // Print mission parameters
    print("uid: \(uid) messageNumber: \(messageNumber) packageName: \(packageName)")
    // Decode mission message
    do {
      let event = try Your_Company_Missions_Mymissions_Airsdk_Messages_Event(serializedData: payload)
      DispatchQueue.main.async {
        print("Event: \(event.id)")
        switch event.id {
        case .event1:
          break
        case .event2:
          break
        }
      }
    } catch {
      print("Failed to extract protobuf data from My mission message")
    }
  }
}

Mission web server REST API#

Instead of using the Ground SDK to manage flight missions, the drone web server REST API can also be used.

For more information see the WebAPI Module Mission.