Connect to a physical drone#

Warning

DISCLAIMER You should really carefully validate your code before trying to control a physical drone through Olympe. Use at your own risk.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PARROT COMPANY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Direct connection to a physical drone over Wi-Fi#

Note

The direct connection to a drone over Wi-Fi (without a SkyController inbetween), is supported for ANAFI and ANAFI USA drones. For ANAFI Ai, the direct connection to the drone is disabled by default and must first be activated from FreeFlight 7 as explained here.

To connect olympe to a physical drone, you first need to connect your Linux computer to a drone Wi-Fi access point. Once you are connected to your drone over Wi-Fi, you just need to specify the drone IP address on its Wi-Fi interface (“192.168.42.1”).

 1import olympe
 2import os
 3
 4DRONE_IP = os.environ.get("DRONE_IP", "192.168.42.1")
 5
 6
 7def test_physical_drone():
 8    drone = olympe.Drone(DRONE_IP)
 9    drone.connect()
10    drone.disconnect()
11
12
13if __name__ == "__main__":
14    test_physical_drone()

Connect to a drone through a SkyController#

To connect Olympe to a SkyController, you first need to connect your Linux computer to the SkyController USB-C port. Then you should be able to connect to your SkyController with its RNDIS IP address (“192.168.53.1”) using the olympe.SkyController class instead of the olympe.Drone class that we’ve been using until now.

1import olympe
2from olympe.messages.skyctrl.CoPiloting import setPilotingSource
3
4if __name__ == "__main__":
5    skyctrl = olympe.SkyController("192.168.53.1")
6    skyctrl.connect()
7    skyctrl(setPilotingSource(source="Controller")).wait()
8    skyctrl.disconnect()

Wi-Fi pairing of a SkyController and a drone#

Wi-Fi pairing a SkyController and a drone means giving the SkyController access to the drone Wi-Fi access point.

A SkyController keeps an internal list of “known” (previously apaired) drones. When a SkyController boots, it scans the visible Wi-Fi SSIDs and identifies known drones Wi-Fi access points. It then tries to connect to a every known drones that are visible until it successfully connects to a drone.

When a SkyController 4 is connected to a drone its frontal LED displays as solid blue.

Note

If you’ve bought your drone and your SkyController together (in the same package bundle), the Wi-Fi pairing of your devices has already been done at the end of factory assembly process and you shouldn’t have to pair your devices.

There are three ways to pair a SkyController and a drone:

  1. USB pairing: just connect the SkyController and the drone with an USB-C <-> USB-C cable. This will actually reset the Wi-Fi security key of the drone before adding the drone and its new security key to the SkyController known drones list.

  2. Using the SDK olympe.messages.drone_manager messages to edit the SkyController known drones list (as demonstrated in the following example)

  3. Performing a SkyController factory reset: when you’ve reset the SkyController to its factory settings, it knows the drone it was paired to at the factory.

In the following example, provided a SkyController and at least one (Wi-Fi) visible drone, we demonstrate how to make the SkyController return its known and visible drones list and how to add one drone to its known drones list (i.e. pairing it).

First we must connect to the SkyController 4 using its IP address (192.168.53.1).

20class SkyControllerExample:
21    def __init__(self):
22        self.skyctrl = olympe.SkyController4(SKYCTRL_IP)
23
24    def skyctrl_connect(self):
25        self.skyctrl.connect()

Then, the update_drone method below updates the known and visible drones list.

27def update_drones(self):
28    discover_results = self.skyctrl(discover_drones()).wait(_timeout=10)
29    assert discover_results.success(), "Update drone discovery timedout"
30    drone_list_items = discover_results.received_events()
31    known_drones = OrderedDict()
32    visible_drones = OrderedDict()
33    active_drone = None
34    for drone_list_item in drone_list_items:
35        if drone_list_item.args["visible"] == 1:
36            visible_drones[drone_list_item.args["serial"]] = drone_list_item.args
37        if drone_list_item.args["active"] == 1:
38            active_drone = drone_list_item.args["serial"]
39        if drone_list_item.args["connection_order"] != 0:
40            known_drones[drone_list_item.args["serial"]] = drone_list_item.args
41
42    self.active_drone = active_drone
43    self.known_drones = known_drones
44    self.visible_drones = visible_drones
45
46    print("Active drone: ", self.active_drone)
47    print("Known drones: ", ", ".join(self.known_drones))
48    print("Visible drones: ", ", ".join(self.visible_drones))

It then prints those two lists and possibly one “Active drone” if the SkyController 4 is currently connected to a drone.

The pair_drone method below takes a drone serial PI number and a Wi-Fi security key and if the requested drone is currently visible will try to pair the SkyController 4 to it if necessary.

50def pair_drone(self, drone_serial, drone_security_key=""):
51    self.update_drones()
52    if drone_serial is None:
53        print("No drone serial provided.")
54        return False
55    if self.active_drone == drone_serial:
56        print(f"SkyController4 is already connected to {drone_serial}")
57        return True
58    print(f"SkyController4 is not currently connected to {drone_serial}")
59    if drone_serial in self.visible_drones:
60        print(f"Connecting to {drone_serial}...")
61        connection = self.skyctrl(
62            connect(serial=drone_serial, key=drone_security_key)
63            >> connection_state(state="connected", serial=drone_serial)
64        ).wait(_timeout=10)
65    elif drone_serial in self.known_drones:
66        print(
67            f"{drone_serial} is a known drone but is not currently visible"
68        )
69        return False
70    else:
71        print(
72            f"{drone_serial} is an unknown drone and not currently visible"
73        )
74        return False
75    if connection.success():
76        print(f"Connected to {drone_serial}")
77        return True
78    else:
79        print(f"Failed to connect to {drone_serial}")
80        return False
81

Finally the forget_drone method below is here to demonstrate how to unpair a drone (forget its SSID and Wi-Fi security key).

82def forget_drone(self, drone_serial):
83    if drone_serial == self.active_drone:
84        print(f"Forgetting {drone_serial} ...")
85        self.skyctrl(
86            forget(serial=drone_serial)
87            >> connection_state(state="disconnecting", serial=drone_serial)
88        ).wait(_timeout=10)
89    elif drone_serial in self.known_drones:
90        print(f"Forgetting {drone_serial} ...")
91        self.skyctrl(forget(serial=drone_serial)).wait(_timeout=10)
92    else:
93        print(f"{drone_serial} is an unknown drone")
94

The main function of this example:

  1. Connects to the SkyController 4

  2. Lists the visible and known drones

  3. If the requested drone is not already paired, pairs it with the SkyController 4

  4. If the drone was not previously paired, forgets it (this example shouldn’t have any persistent side effect).

  5. Disconnects from the SkyController (the SkyController itself may still be connected to the drone though).

 99def main():
100    example = SkyControllerExample()
101    print("@ Connection to SkyController")
102    example.skyctrl_connect()
103    example.update_drones()
104    if DRONE_SERIAL not in example.known_drones:
105        print("@ Connection to a drone")
106        if example.pair_drone(DRONE_SERIAL, DRONE_SECURITY_KEY):
107            example.update_drones()
108        print("@ Forgetting a drone")
109        example.forget_drone(DRONE_SERIAL)
110        example.update_drones()
111    print("@ Disconnection from SkyController")
112    example.disconnect_skyctrl()

Cellular (4G) pairing of a SkyController and a drone#

Note

Cellular pairing is only available for 4G capable drones. This currently includes ANAFI Ai.

Like for Wi-Fi pairing, cellular pairing a SkyController 4 and a drone means giving the SkyController access to the drone cellular modem interface. From the point of view of the controller (here Olympe), a SkyController 4 paired with a drone cellular interface acts as a multi-path passthrough proxy to the drone (Wi-Fi + cellular).

Note

While ANAFI Ai has a cellular modem, the SkyController 4 does not have one and the controller (the Olympe host) is used as an Internet gateway to reach the drone cellular interface.

Cellular pairing is only possible once the Wi-Fi pairing has previously been performed and when the SkyController 4 is connected to the ANAFI Ai over Wi-Fi.

27skyctrl = olympe.SkyController(SKYCTRL_IP)
28# Connect to skycontroller
29assert skyctrl.connect()
30print("- SkyController connected")
31
32# Wait for the skycontroller and the drone to be connected
33skyctrl(connection_state(state="connected")).wait()

In the above example, at line 33 we check that the SkyController 4 is currently connected to a drone over Wi-Fi. We can then print the current status of the SkyController 4 cellular link to the drone (the cellular link status should not be ‘RUNNING’ at this point).

36# Get the cellular link status before pairing
37skyctrl(network.Event.State(
38    links_status=network.LinksStatus(links=[
39        network.LinksStatus.LinkInfo(type=LinkType.LINK_TYPE_CELLULAR)]
40    )
41)).wait()
42links = skyctrl.get_state(network.Event.State)["links_status"]["links"]
43cellular_link = next(
44    filter(lambda link: link["type"] == LinkType.LINK_TYPE_CELLULAR, links), None
45)
46print(f"    cellular link status: {cellular_link['status']}")

To start the cellular pairing process, just call skyctrl.cellular.pair():

48# Pair the SkyController and the Drone in cellular with a new anonymous user APC token
49print("- Cellular pairing of the SkyController and the Drone")
50token = skyctrl.cellular.pair()

A newly created APC pairing token is returned by this function. This APC pairing token is valid for a maximum of 2 years provided that at least one drone is associated with this token.

Warning

Do not expose your pairing APC token. The token is protecting the access to your drone and should be kept secret.

With your APC pairing token in your possession, you can now configure your SkyController 4 to use it: skyctrl.cellular.configure(token).

53print("- Connect cellular using the new user APC token")
54skyctrl.cellular.configure(token)

Note

The SkyController does not store any token. You should (re-)configure a token each time you reboot your SkyController.

Once the SkyController has been configured with a token it will automatically try to connect to your ANAFI Ai using the cellular link. We can now wait for the ‘RUNNING’ cellular link status.

58# Wait for cellular link status pass to Link Status.running
59skyctrl(network.Event.State(
60    links_status=network.LinksStatus(links=[
61        network.LinksStatus.LinkInfo(type=LinkType.LINK_TYPE_CELLULAR, status=LinkStatus.running)]
62    )
63)).wait()
64links = skyctrl.get_state(network.Event.State)["links_status"]["links"]
65cellular_link = next(
66    filter(lambda link: link["type"] == LinkType.LINK_TYPE_CELLULAR, links), None
67)
68
69# Log cellular link status
70print(f"    cellular link status: {cellular_link['status']}")

The SkyController is now using both the Wi-Fi and the cellular link for all network traffic with the drone.

Commands passthrough and manual piloting#

By default, the SkyController keeps the control over the manual piloting commands with its joysticks. For every other command and event messages, the SkyController mainly acts as a passthrough proxy to the drone it is connected to. If you want Olympe to be able to send manual piloting commands you should tell the SkyController that the “Controller” (i.e. Olympe) should be the only source of manual piloting commands using the olympe.messages.skyctrl.CoPiloting.setPilotingSource() command message

5skyctrl = olympe.SkyController("192.168.53.1")
6skyctrl.connect()
7skyctrl(setPilotingSource(source="Controller")).wait()
8skyctrl.disconnect()