Example Drone-Client Communication with Python

This demo shows a simple Drone-Client interaction via the Dromt GRPC server. We tested this demo on Ubuntu 20.04 with Python 3.8.10. We recommend creating a clean Python environment.

Prerequisites

  1. Copy the code below to ./demo.py

  2. Create an account on https://stream.dromt.it

  3. Download your client certificate from the menu in the top-right corner and place it under ./data/cert_client.pfx

  4. Create a drone and copy its identifier in demo.py, setting the DRONE_ID variable

  5. Download the drone certificate from the dashboard and place it under ./data/cert_drone.pfx

Install Dependencies and Generate GRPC Python files

# Install dependencies
pip install cryptography==39.0.1 grpcio==1.51.1 protobuf==4.21.12 grpcio-tools==1.51.1

# Get .proto files
PROTO_URL=https://docs.dromt.it/static/dromt_proto.zip
wget $PROTO_URL -O proto.zip && unzip proto.zip && rm -rf proto.zip

# Generate Python files
python -m grpc_tools.protoc -Igrpc/protocol \
    --python_out=./grpc \
    --grpc_python_out=./grpc \
    common.proto \
    client.proto \
    drone.proto

Run

# On the first shell, run the drone application
python demo.py drone

# On the second shell, run the client application
python demo.py client

# Enjoy!

Full Python Code

import asyncio
import grpc
import sys

from cryptography.hazmat.primitives.serialization.pkcs12 import load_key_and_certificates
from cryptography.hazmat.primitives import serialization

sys.path.append("grpc") # load GRPC files
import common_pb2

MAX_MESSAGE_SIZE = 10_000_000 # Set a maximum size for GRPC messages, in bytes
DRONE_ID = "<your_drone_id>" # get your ID from the web dashboard

def get_credentials(cert_file):
    with open(cert_file, "rb") as f:
        data = f.read()

    key, cert, _ = load_key_and_certificates(data, None)

    # encode key in PEM format
    key_bytes = key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.NoEncryption()
    )

    # encode certificate in PEM format
    cert_bytes = cert.public_bytes(
        encoding=serialization.Encoding.PEM
    )

    return key_bytes, cert_bytes

# Function that generates ClientData objects to be sent to the GRPC server
async def client_stream(session):
    while True:
        await asyncio.sleep(1)

        data = common_pb2.ClientData(
            session=session,
            command=common_pb2.Command(
                id=1
            )
        )

        yield data

# Function that generates DroneData objects to be sent to the GRPC server
async def drone_stream(session):
    while True:
        await asyncio.sleep(1)

        data = common_pb2.DroneData(
            session=session,
            log=common_pb2.Log(
                message="Hello!"
            )
        )

        yield data

async def main(mode, key_file, session, stream_func):
    # Get certificate and private key from PKCS12 ".pfx" file
    # Both should be represented as byte arrays serialized in PEM format.
    key, cert = get_credentials(key_file)

    # Set credentials
    cred = grpc.ssl_channel_credentials(
                certificate_chain=cert, # drone's certificate
                private_key=key # drone's private key
            )

    # Create GRPC channel
    async with grpc.aio.secure_channel(
        f"{mode}.grpc.dromt.it:443",
        cred,
        options=[
            ('grpc.max_send_message_length', MAX_MESSAGE_SIZE),
            ('grpc.max_receive_message_length', MAX_MESSAGE_SIZE),
        ]
    ) as channel:
        try:
            # create stub
            stub = pb2_grpc.StreamerStub(channel)

            # register session, receiving a new session object with the session ID.
            session = await stub.Register(session)

            # now we are connected
            print("Connected to GRPC server.")

            # opening stream sending ClientData objects.
            async for data in stub.Stream(stream_func(session)):
                # handle DroneData object received from the GRPC server
                print(f"Received {data.WhichOneof('data')}")
        except Exception as e:
            # An exception occurred
            print(f"Exception: {e}")

if len(sys.argv) != 2:
    print(f"usage: {sys.argv[0]} {{client,drone}}")
    sys.exit(-1)
mode = sys.argv[1]

if mode == "client":
    import client_pb2_grpc as pb2_grpc
    key_file = "data/cert_client.pfx"
    stream_func = client_stream
    session = common_pb2.ClientSession(droneId=DRONE_ID)
elif mode == "drone":
    import drone_pb2_grpc as pb2_grpc
    key_file = "data/cert_drone.pfx"    
    stream_func = drone_stream
    session = common_pb2.DroneSession()
else:
    print("Invalid mode: choose between 'client' and 'drone'")
    sys.exit(-1)

try:
    asyncio.run(main(mode, key_file, session, stream_func))
except KeyboardInterrupt:
    print("Closing")