Drone control - ManuJackPel/Brain-Powered-2022-2023 GitHub Wiki

The Parrot AR drone 2.0 has a dedicated python library,pyardrone, which can be found via this link: https://pyardrone.readthedocs.io/en/latest/intro/. The main takeaway of the script below is to demonstrate how the TCP connection works via the socket library and thus the drone can receive commands which can be translated into movement. There is also an abort option when ctrl + c is pressed.

Commanding the drone

Firstly we import the necessary libraries.

import signal
import time
import socket
from pyardrone import ARDrone

Configuring and initiliazing the ARDrone Object. Further explanation of what the parameters mean can be found here

# Configuration variables
DRONE_HOST = '192.168.1.1'
AT_PORT = 5556
NAVDATA_PORT = 5554

# Initialize the drone
drone = ARDrone(host=DRONE_HOST, at_port=AT_PORT, navdata_port=NAVDATA_PORT, watchdog_interval=0.5, timeout=0.01, bind=True, connect=True)
drone.navdata_ready.wait()  # wait until NavData is ready

We need to provide a shutdown mechanism for the drone, safeguarding it from abrupt terminations that might otherwise leave it in an unpredictable state. Registered using signal.signal(signal.SIGINT, signalhandler), it's designed to capture keyboard interrupts (specifically when the user presses Ctrl+C). Instead of the script terminating immediately upon such an interrupt, the custom signalhandler function is called, which in turn calls the land() function, ensuring the drone safely lands before the script ends.

# Define what should happen when terminating program
def signalhandler(sig,frame):
    drone.land()
    print('landing...')
    raise KeyboardInterrupt

# Run the function when receiving the termination signal
signal.signal(signal.SIGINT, signalhandler)

We start the drone using the drone.takeoff() command. Once airborne, the program continually calls the receive_condition() function. Based on this received condition, the drone's movement is determined. If the condition value is 1, the program pauses for a second and then directs the drone to move to the right at a speed or intensity of 0.5. Conversely, if the condition is 0, the drone is instructed to move to the left at the same speed after a similar pause. In the event that the receive_condition() function returns values other than 1 or 0, the drone doesn't receive any new movement instructions and continues with its current state until a recognized condition is received.


drone.takeoff()
while True:
	condition = receive_condition() # place holder
	if int(condition) == 1:
		time.sleep(1)
		drone.move(right=0.5)
	elif int(condition)== 0:
		time.sleep(1)
		drone.move(left=0.5)

Sending drone commands to different PC

Server/Sending PC

One PC will be responsible for classifying the EEG data and producing an appropriate command. The script firstly connects to the drone and to the TCP socket. The host is the ip of your machine that you are using. The port can be set to 9999. It then waits for input inside of the while-loop. When an input is received, the drone moves either right or left for 0.5 seconds. We first import the socket module, which provides the tools to create and interact with network sockets. ANd the time module, which provides time-related functions. It's used in this script to create a pause between sending messages.

import socket
import time

Specify the IP adresss and port number of the computer running the server script.

SERVER_HOST = 192.168.1.21
SERVER_PORT = 9999

Create a new socket object, specify that it an IPv4('AF_INET') socket using the TCP Protocol('SOCK_STREAM'). Bind the socket to the specified IP adresss and port number. Then tell the socket to start listening to connections, we specified a maximum of 5 but 1 is sufficient. When the socket accepts an incoming connection it returns a new socket (client_sock) representing the client and the address (client_address) of the client. We can then send a encoded message to the client using it's socket each second.

def start_server():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind((SERVER_HOST, SERVER_PORT))
    server_socket.listen(5)
    print(f"Listening for connections on {SERVER_HOST}:{SERVER_PORT}")
    
    client_sock, client_address = server_socket.accept()
    print(f"Accepted connection from {client_address}")
    
    while True:
        message = "1"
        client_sock.send(message.encode('ascii'))
        time.sleep(1)

Client/Receiving PC

The other PC is responsible for receiving the command and controlling the drone. The steps for establishing a client are similar to a server. We also make use of the socket module.

import socket

We define the port adress and IP of the server the PC should connect to.

SERVER_HOST = 192.168.1.21
SERVER_PORT = 9999

We make another socket object using IPv4 and TCP/IP. Instead of binding to the IP adress and port we connect to it. If the connection is successful, the socket object is returned, allowing for future communication with the server. We can then use the socket object to receive up to 1024 bytes at a time. Once the data is received, it's decoded from ASCII format and stored.

def connect_to_server():
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect((SERVER_HOST, SERVER_PORT))
    return client_socket

sock = connect_to_server()

while True:
    data = sock.recv(1024).decode('ascii')
    print(f"Received: {data}")

Combined Script for receiving commands and instructing the drone

Below is an example of what a script might look like for the PC which is both receiving commands and routing them to the drone. An additional line is added to make sure that the connections is re-established after seperation.

import signal
import time
import socket
from pyardrone import ARDrone

# Configuration variables
DRONE_HOST = '192.168.1.1'
AT_PORT = 5556
NAVDATA_PORT = 5554
SERVER_HOST = "192.168.4.20"
SERVER_PORT = 9999

# Initialize the drone
drone = ARDrone(host=DRONE_HOST, at_port=AT_PORT, navdata_port=NAVDATA_PORT, watchdog_interval=0.5, timeout=0.01, bind=True, connect=True)
drone.navdata_ready.wait()  # wait until NavData is ready

def connect():
    global server
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.connect((SERVER_HOST, SERVER_PORT))

def signalhandler(sig,frame):
    drone.land()
    print('landing...')
    raise KeyboardInterrupt

if __name__ == "__main__":
    # Set up a signal handler to handle keyboard interrupt (Ctrl+C) and land the drone before terminating the script
    signal.signal(signal.SIGINT, signalhandler)

    connect()
    drone.takeoff()
    print('taking off..')

    while True:
        try:
            condition = server.recv(1024)
            condition = condition.decode('ascii')
            print(condition)
            if len(condition) == 0:
                print("Disconnected!")
                connect()

        except (ConnectionResetError, TimeoutError):
            print("host offline, reconnecting..")
            connect()
       
        if int(condition) == 1:
            drone.move(right = 0.5)
            time.sleep(1)
        elif int(condition)== 0:
            drone.move(left = 0.5)
            time.sleep(1)