Sending Telemetry (UDP)

UDP Transport

Telemetry is sent in the form of packets (datagrams) with a specific encoding to the following host on port 16060 using UDP:

api-udp-telemetry.airmap.com UDP/16060

UDP Encoding

Each UDP datagram consists of a manually constructed package header with a payload that may contain multiple messages, each prefixed with a message header. All numbers are network order (big endian).

Package Header Format

The package header contains a packet serial number, flight ID, flight ID length, encryption type, initialization vector, and encrypted payload. The payload contains the actual message, which is a serialized protocol buffer byte string.

Position
Bytes
Type
Name
Description

1

4

uint32

Serial Number

Packet Serial Number, starts at 1, increments by 1 per packet sent from client during this session (start-comm to end-comm)

2

1

uint8

Flight ID Length

The length of the flight ID

3

1<x<256

String

Flight ID

The flight ID

4

1

uint8

Encryption Type

An ID for an AirMap supported encryption type (currently, only 'aes-256-cbc' is supported)

5

16

raw

Initialization Vector

Initialization Vector used for aes-256-cbc encryption

6

1<x<64k

raw

Payload

Encrypted payload (byte concatenated, serialized messages, each prefixed by a Message Header)

Message Header Format

The payload in the package header consists of byte concatenated, serialized messages which are prefixed by a message header. Each message header contains the message type ID, message length, and message.

Position
Bytes
Type
Name
Description

1

2

uint16

Message Type ID

Numeric Message Type ID

2

2

uint16

Message Length

Serialized message length, for a max message size of 64kB

3

1<x<64x

raw

Message

Serialized protocol buffer byte string

Message Types

The telemetry payload is structured in the form of messages, which can relay data regarding the position, speed, orientation, or pressure of an aircraft. The supported messages types and their respective message IDs are listed below. Multiple messages and message types can be concatenated and serialized to form the payload.

Message Type ID
Message

1

Position

2

Speed

3

Altitude

4

Barometer

1. Position

Geographical position of the aircraft

Type
Name
Description

uint64

timestamp

UNIX time in milliseconds

float64

latitude

The recorded latitude

float 64

longitude

The recorded longitude

float32

altitude_msl

Altitude above mean sea level (ie. GPS), meters

float32

altitude_gl

Altitude above ground level, meters

float32

horizontal_accuracy

Horizontal Dilution of Precision (HDOP, http://gpsinformation.net/main/altitude.htm), in meters

2. Speed

Aircraft speed in meters per second using NED ground reference frame

Type
Name
Description

uint64

timestamp

UNIX time in milliseconds

float32

velocity_x

Aircraft Speed in the x direction in meters per second using the North-East-Down (N-E-D) coordinate system

float32

velocity_y

Aircraft Speed in the y direction in meters per second using the North-East-Down (N-E-D) coordinate system

float32

velocity_z

Aircraft Speed in the z direction in meters per second using the North-East-Down (N-E-D) coordinate system

3. Attitude

Orientation of the aircraft in NED ground reference frame

Type
Name
Description

uint64

timestamp

UNIX time in milliseconds

float32

yaw

Yaw angle measured from True North, { 0 <= x < 360 } degrees

float32

pitch

Pitch angle, { -180 < x <= 180 } degrees

float32

roll

Roll angle, { -180 < x <= 180 } degrees

4. Barometer

Barometric pressure

Type
Name
Description

uint64

timestamp

UNIX time in milliseconds

float32

pressure

Barometric pressure in hPa

Security

Payload encryption is supported with the following encryption types:

ID
Type

1

aes-256-cbc

Protocol Buffers

The telemetry payload message format is defined in a .proto file, where each message type is specified.

syntax = "proto3";
 
package airmap.telemetry;
 
message Position {
     
    // UNIX time in Milliseconds
    uint64 timestamp = 1;
 
    // The recorded latitude
    // Decimal place requirement: 7 decimal places.
    double latitude = 2;
 
    // The recorded longitude
    // Decimal place requirement: 7 decimal places.
    double longitude = 3;
 
    //Altitude above mean sea level (ie. GPS), meters
    float altitude_agl = 4;
 
    // Altitude above ground level, meters
    float altitude_msl = 5;
     
    // Horizontal Dilution of Precision, in meters
    float horizontal_accuracy = 6;
}
 
message Attitude {
     
    // UNIX time in Milliseconds
    uint64 timestamp = 1;
 
    // Yaw angle measured from True North, { 0 <= x < 360 } degrees
    float yaw = 2;
 
    // Pitch angle, { -180 < x <= 180 } degrees
    float pitch = 3;
 
    // Roll angle, { -180 < x <= 180 } degrees
    float roll = 4;
}
 
message Speed {
     
    // UNIX time in Milliseconds
    uint64 timestamp = 1;
 
    // Aircraft Speed in the x direction in meters per second using the North-East-Down (N-E-D) coordinate system
    float velocity_x = 2;
 
     // Aircraft Speed in the y direction in meters per second using the North-East-Down (N-E-D) coordinate system
    float velocity_y = 3;
 
     // Aircraft Speed in the z direction in meters per second using the North-East-Down (N-E-D) coordinate system
    float velocity_z = 4;
}
 
message Barometer {
 
    // UNIX time in Milliseconds
    uint64 timestamp = 1;
 
    // Barometric pressure in hPa
    float pressure = 2;
}

Example (Python)

In this example, using Python, the file telemetry_pb2.py is generated by running the protocol buffer compiler protoc.

In the main application, telemetry.py, a token is first obtained using some authentication method (in this case, Anonymous User). A flight (with unique flight id returned) is then created by using the Flight API and a communication link is initiated with the /start-comm endpoint. This returns a secret key, which is decoded from base64 and subsequently used to encrypt the payload.

The payload itself is generated by using the generated telemetry_pb2.py file and concatenating several constructed message type objects. These objects are populated with data from the flight and packed into a serialized message, which is prefixed by a message header. The messages are then encrypted and added to the package header. Finally, several packets are sent over a UDP stream to api-udp-telemetry.airmap.com on port 16060 and communications are ended with the /end-comm endpoint.

import socket
import sys
import struct
import requests
import json
import telemetry_pb2
import base64
import time
from time import sleep
from datetime import datetime
from Crypto import Random
from Crypto.Cipher import AES

# simple sawtooth wave simulation

class Simulator:
    def update(self, val, dt, mx, initval): 
        val = val + dt
        if val > mx: 
            val = initval 
        return val  

    def getTimestamp(self):
        d = datetime.now()
        return d.microsecond/1000 + d.second*1000 

    def getLattitude(self):
        self._lat = self.update(self._lat, 0.002, 34.02, 34.015802)
        return  self._lat
    def getLongtitude(self):
        self._lon = self.update(self._lon, 0.002, -118.44, -118.451303) 
        return self._lon
    def getAgl(self):
        self._agl = self.update(self._agl, 1.0, 100.0, 0.0) 
        return self._agl
    def getMsl(self): 
        self._msl = self.update(self._msl, 1.0, 100.0, 0.0) 
        return self._msl
    def getHorizAccuracy(self):
        self._horizAccuracy = self.update(self._horizAccuracy, 1.0, 10.0, 0.0) 
        return self._horizAccuracy
    def getYaw(self):
        self._yaw = self.update(self._yaw, 1.0, 360.0, 0.0) 
        return self._yaw
    def getPitch(self): 
        self._pitch = self.update(self._pitch, 1.0, 90.0, -90.0) 
        return self._pitch
    def getRoll(self): 
        self._roll = self.update(self._roll, 1.0, 90.0, -90.0) 
        return self._roll
    def getVelocityX(self): 
        self._velocity_x = self.update(self._velocity_x, 1.0, 100.0, 10.0) 
        return self._velocity_x
    def getVelocityY(self): 
        self._velocity_y = self.update(self._velocity_y, 1.0, 100.0, 10.0) 
        return self._velocity_y 
    def getVelocityZ(self): 
        self._velocity_z = self.update(self._velocity_z, 1.0, 100.0, 10.0) 
        return self._velocity_z
    def getPressure(self): 
        self._pressure = self.update(self._pressure, 0.1, 1013.0, 1012.0) 
        return self._pressure

    _lat = 34.015802;
    _lon = -118.451303;
    _agl = 0.0;
    _msl = 0.0;
    _horizAccuracy = 0.0;
    _yaw = 0.0;
    _pitch = -90.0;
    _roll = -90.0;
    _velocity_x = 10.0;
    _velocity_y = 10.0;
    _velocity_z = 10.0;
    _pressure = 1012.0;

# anonymous user (returns JWT)

def get_token(api_key, user_id):
    try:
        response = requests.post(
            url="https://api.airmap.com/auth/v1/anonymous/token",
            headers={
                "X-API-Key": api_key
            },
            data={"user_id": user_id}
        )
        if response.status_code == 200:
            return str(json.loads(response.content)["data"]["id_token"])
        else:
            print('Response HTTP Status Code: {status_code}'.format(status_code=response.status_code))
            print('Response HTTP Response Body: {content}'.format(content=response.content))
            return -1
    except requests.exceptions.RequestException:
        return -1

# create flight (returns flightID)

def create_flight(latitude, longitude, api_key, token):
    try:
        response = requests.post(
            url="https://api.airmap.com/flight/v2/point",
            headers={
                "X-API-Key": api_key,
                "Authorization": "Bearer "+token,
                "Content-Type": "application/json; charset=utf-8"
            },
            data=json.dumps({
                "latitude": latitude,
                "longitude": longitude
            })
        )
        if response.status_code == 200:
            return str(json.loads(response.content)["data"]["id"])
        else:
            print('Response HTTP Status Code: {status_code}'.format(status_code=response.status_code))
            print('Response HTTP Response Body: {content}'.format(content=response.content))
            return -1
    except requests.exceptions.RequestException:
        return -1

# start comm (returns secretKey)

def start_comm(api_key, token, flightID):
    try:
        response = requests.post(
            url="https://api.airmap.com/flight/v2/"+flightID+"/start-comm",
            headers={
                "X-API-Key": API_KEY,
                "Authorization": "Bearer "+token
            }
        )
        if response.status_code == 200:
            return str(json.loads(response.content)["data"]["key"])
        else:
            print('Response HTTP Status Code: {status_code}'.format(status_code=response.status_code))
            print('Response HTTP Response Body: {content}'.format(content=response.content))
            return -1
    except requests.exceptions.RequestException:
        return -1

# end comm (returns 0)

def end_comm(api_key, token, flightID):
    try:
        response = requests.post(
            url="https://api.airmap.com/flight/v2/"+flightID+"/end-comm",
            headers={
                "X-API-Key": API_KEY,
                "Authorization": "Bearer "+token
            }
        )
        if response.status_code == 200:
            return 0
        else:
            print('Response HTTP Status Code: {status_code}'.format(status_code=response.status_code))
            print('Response HTTP Response Body: {content}'.format(content=response.content))
            return -1
    except requests.exceptions.RequestException:
        return -1

# end flight (returns 0)

def end_flight(api_key, token, flightID):
    try:
        response = requests.post(
            url="https://api.airmap.com/flight/v2/"+flightID+"/end",
            headers={
                "X-API-Key": api_key,
                "Authorization": "Bearer "+token
            }
        )
        if response.status_code == 200:
            return 0
        else:
            print('Response HTTP Status Code: {status_code}'.format(status_code=response.status_code))
            print('Response HTTP Response Body: {content}'.format(content=response.content))
            return -1
    except requests.exceptions.RequestException:
        return -1

# API key and user ID

API_KEY = "{API KEY}"
USER_ID = "{USER ID}"

# coordinates

latitude = 34.0382223095064
longitude = -118.45424652099608

# get token, create flight, and start comm

JWT = get_token(API_KEY, USER_ID)
if JWT == -1:
    print("Error with authentication")
    exit(1)

flightID = create_flight(latitude, longitude, API_KEY, JWT)
if flightID == -1:
    print("Error creating flight")
    exit(1)

secretKey = start_comm(API_KEY, JWT, flightID)
if secretKey == -1:
    print("Error starting communication")
    exit(1)

# decode key

secretKey = base64.b64decode(secretKey)

# messages

position    = telemetry_pb2.Position()
attidute    = telemetry_pb2.Attitude()
speed       = telemetry_pb2.Speed()
barometer   = telemetry_pb2.Barometer()

# addressing information of target
HOSTNAME = 'api-udp-telemetry.airmap.com'
IPADDR = socket.gethostbyname(HOSTNAME)
PORTNUM = 16060
 
# initialize a socket, SOCK_DGRAM specifies that this is UDP
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)

sim = Simulator()

try:
    # connect the socket
    s.connect((IPADDR, PORTNUM))

    # send 100 messages at 5Hz
    for i in range(10):

        # update messages 
        timestamp = sim.getTimestamp()
        position.timestamp              = timestamp
        position.latitude               = sim.getLattitude()
        position.longitude              = sim.getLongtitude()
        position.altitude_agl           = sim.getAgl()
        position.altitude_msl           = sim.getMsl()
        position.horizontal_accuracy    = sim.getHorizAccuracy()

        attidute.timestamp              = timestamp
        attidute.yaw                    = sim.getYaw()
        attidute.pitch                  = sim.getPitch()
        attidute.roll                   = sim.getRoll()

        speed.timestamp                 = timestamp
        speed.velocity_x                = sim.getVelocityX()
        speed.velocity_y                = sim.getVelocityY()
        speed.velocity_z                = sim.getVelocityZ()

        barometer.timestamp             = timestamp
        barometer.pressure              = sim.getPressure()

        # build  payload

        # serialize  protobuf messages to string and pack to payload buffer
        bytestring = position.SerializeToString()
        format = '!HH'+str(len(bytestring))+'s'
        payload = struct.pack(format, 1, len(bytestring), bytestring)

        bytestring = attidute.SerializeToString()
        format = '!HH'+str(len(bytestring))+'s'
        payload += struct.pack(format, 2, len(bytestring), bytestring)

        bytestring = speed.SerializeToString()
        format = '!HH'+str(len(bytestring))+'s'
        payload += struct.pack(format, 3, len(bytestring), bytestring)

        bytestring = barometer.SerializeToString()
        format = '!HH'+str(len(bytestring))+'s'
        payload += struct.pack(format, 4, len(bytestring), bytestring)

        # encrypt payload

        # use PKCS7 padding with block size 16
        BS = 16
        pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
        payload = pad(payload)
        IV = Random.new().read(16)
        aes = AES.new(secretKey, AES.MODE_CBC, IV)
        encryptedPayload = aes.encrypt(payload)

        # send telemetry

        # packed data content of the UDP packet
        format = '!LB'+str(len(flightID))+'sB16s'+str(len(encryptedPayload))+'s'
        PACKETDATA = struct.pack(format, 1, len(flightID), flightID, 1, IV, encryptedPayload)

        # send the payload
        s.send(PACKETDATA)

        # print timestamp when payload was sent
        print "Sent payload messsage #" , i+1 ,  "@" , time.strftime("%H:%M:%S") 

        # 5 Hz
        sleep(0.2)

except:
    exit(1)
 
# close the socket
s.close()

# end comm and flight
if (end_comm(API_KEY, JWT, flightID) == -1):
    print("Error ending communication")
    exit(1)

if (end_flight(API_KEY, JWT, flightID) == -1):
    print("Error ending flight")
    exit(1)

Example (C++)

This example, using C++, also shows a simple implementation of sending several packets of telemetry similar to the Python example above. Each payload contains data of all message types.

The example makes use of the following libraries:

  • libcurl
  • protobuf
  • cryptopp
  • nholmann/json
#include <string>
#include <cstdint>
#include <unistd.h>
// udp
#include <netdb.h>
// libcurl -- https://curl.haxx.se/libcurl/
#include <curl/curl.h>
// protobuf -- https://github.com/google/protobuf
#include "telemetry.pb.h"
// json -- https://github.com/nlohmann/json
#include "json.hpp"
// crypto -- https://www.cryptopp.com/
#include <cryptopp/aes.h>
#include <cryptopp/base64.h>
#include <cryptopp/ccm.h>
#include <cryptopp/filters.h>
#include <cryptopp/osrng.h>

// commands to build with g++
// g++ -std=c++1y -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/telemetry.d" -MT"src/telemetry.o" -o "src/telemetry.o" "../src/telemetry.cpp"
// g++  -o "telemetry"  ./src/telemetry.o ./src/telemetry.pb.o   -lcurl -lcryptopp -lprotobuf

// simple sawtooth wave simulation
#define BUMP(val, dt, mx, initval) val += dt; if (val > mx) val = initval; return val;

class Simulator
{
public:
    Simulator() {
        init();
    }
    void init() {
        m_lat = 34.015802;
        m_lon = -118.451303;
        m_agl = 0.0;
        m_msl = 0.0;
        m_horizAccuracy = 0.0;
        m_yaw = 0.0;
        m_pitch = -90.0;
        m_roll = -90.0;
        m_velocity_x = 10.0;
        m_velocity_y = 10.0;
        m_velocity_z = 10.0;
        m_pressure = 1012.0;
    }

    std::uint64_t getTimeStamp() {
        struct timeval tp;
        gettimeofday(&tp, NULL);
        return static_cast<std::uint64_t>(tp.tv_sec) * 1000L + tp.tv_usec / 1000;
    }

    double getLattitude() { BUMP(m_lat, 0.002, 34.02, 34.015802) }
    double getLongtitude() { BUMP(m_lon, 0.002, -118.44, -118.451303) }
    float getAgl() { BUMP(m_agl, 1.0, 100.0, 0.0) }
    float getMsl() { BUMP(m_msl, 1.0, 100.0, 0.0) }
    float getHorizAccuracy() { BUMP(m_horizAccuracy, 1.0, 10.0, 0.0) }
    float getYaw() { BUMP(m_yaw, 1.0, 360.0, 0.0) }
    float getPitch() { BUMP(m_pitch, 1.0, 90.0, -90.0) }
    float getRoll() { BUMP(m_roll, 1.0, 90.0, -90.0) }
    float getVelocityX() { BUMP(m_velocity_x, 1.0, 100.0, 10.0) }
    float getVelocityY() { BUMP(m_velocity_y, 1.0, 100.0, 10.0) }
    float getVelocityZ() { BUMP(m_velocity_z, 1.0, 100.0, 10.0) }
    float getPressure() { BUMP(m_pressure, 0.1, 1013.0, 1012.0) }

private:
    // position
    double m_lat;
    double m_lon;
    float m_agl;
    float m_msl;
    float m_horizAccuracy;
    // attitude
    float m_yaw;
    float m_pitch;
    float m_roll;
    // speed
    float m_velocity_x;
    float m_velocity_y;
    float m_velocity_z;
    // baro
    float m_pressure;
};

// Communicator
class Communicator
{
public:
    Communicator(const std::string& apiKey) {
        m_url = "https://api.airmap.com";
        m_headers = NULL;
        m_headers = curl_slist_append(m_headers, ("X-API-KEY: " + apiKey).c_str());
        curl_global_init(CURL_GLOBAL_DEFAULT);
    }

    int authenticate(std::string user_id) {
        std::string url = m_url + "/auth/v1/anonymous/token";
        std::string res;
        if (CURLE_OK != curl_post(url.c_str(), m_headers, ("user_id=" + user_id).c_str(), res))
            return -1;
        // parse result to get token
        auto j = nlohmann::json::parse(res);
        try {
            std::string token = j["data"]["id_token"];
            m_headers = curl_slist_append(m_headers, ("Authorization: Bearer " + token).c_str());
        }
        catch (...) {
            return -1; // failed authentication!
        }
        return 0;
    }

    int create_flight(float latitude, float longitude, std::string& flightID) {
        std::string url = m_url + "/flight/v2/point";
        m_headers = curl_slist_append(m_headers, "Accept: application/json");
        m_headers = curl_slist_append(m_headers, "Content-Type: application/json");
        m_headers = curl_slist_append(m_headers, "charsets: utf-8");
        std::string res;
        if (CURLE_OK != curl_post(url.c_str(), m_headers, ("{\"latitude\":" + std::to_string(latitude) + ",\"longitude\":" + std::to_string(longitude) + "}").c_str(), res))
            return -1;
        // parse result to get flightID
        auto j = nlohmann::json::parse(res);
        try {
            flightID = j["data"]["id"];
        }
        catch (...) {
            return -1; // failed flight creation!
        }
        return 0;
    }

    int start(std::string flightID, std::string& key) {
        std::string url = m_url + "/flight/v2/" + flightID + "/start-comm";
        std::string res;
        if (CURLE_OK != curl_post(url.c_str(), m_headers, "", res))
            return -1;
        // parse result to get key
        auto j = nlohmann::json::parse(res);
        try {
            key = j["data"]["key"];
        }
        catch (...) {
            return -1; // failed communication!
        }
        return 0;
    }

    void end(std::string flightID) {
        std::string url = m_url + "/flight/v2/" + flightID + "/end-comm";
        std::string res;
        curl_post(url.c_str(), m_headers, "", res);
    }

    void end_flight(std::string flightID) {
        std::string url = m_url + "/flight/v2/" + flightID + "/end";
        std::string res;
        curl_post(url.c_str(), m_headers, "", res);
    }

private:
    static size_t writeFunction(void* ptr, size_t size, size_t nmemb, std::string* data) {
        data->append((char*)ptr, size * nmemb);
        return size * nmemb;
    }

    unsigned int curl_post(const char* url, const struct curl_slist* headers, const char* postfields, std::string& response) {
        CURLcode res = CURLE_FAILED_INIT;
        CURL *curl = curl_easy_init();

        if (curl) {
            curl_easy_setopt(curl, CURLOPT_URL, url);
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields);
            curl_easy_setopt(curl, CURLOPT_POST, 1L);
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Communicator::writeFunction);
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

            res = curl_easy_perform(curl);
            if (res != CURLE_OK)
                fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));

            curl_easy_cleanup(curl);
        }

        return res;
    }

    struct curl_slist *m_headers;
    std::string m_url;
};

// UdpSender
class UdpSender
{
public:
    UdpSender(const char* host, const int port) : m_host(host), m_port(port), m_sockfd(-1) {}
    int connect() {
        // create socket
        m_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (m_sockfd < 0)
            return -1;

        // get server
        struct hostent *server = gethostbyname(m_host);
        if (server == NULL)
            return -1;

        // set port and IP protocol
        bzero(&m_serveraddr, sizeof(m_serveraddr));
        m_serveraddr.sin_family = AF_INET;
        bcopy(server->h_addr, &m_serveraddr.sin_addr.s_addr, server->h_length);
        m_serveraddr.sin_port = htons(m_port);

        return 0;
    }

    int sendmsg(const char* msg, int msglen) {
        if (m_sockfd < 0)
            return -1;
        return sendto(m_sockfd, msg, msglen, 0, (struct sockaddr*)&m_serveraddr, sizeof(m_serveraddr));
    }

private:
    const char* m_host;
    int  m_port;
    int m_sockfd;
    struct sockaddr_in m_serveraddr;
};

// Buffer
class Buffer
{
public:
    void clear() {
        m_buffer.clear();
    }

    template <typename T>
    Buffer& add(const T& value) {
        return add(reinterpret_cast<const char*>(&value), sizeof(value));
    }

    Buffer& add(const std::string& s) {
        return add(s.c_str(), s.size());
    }

    Buffer& add(const std::vector<std::uint8_t>& v) {
        return add(reinterpret_cast<const char*>(v.data()), v.size());
    }

    Buffer& add(const char* value, std::size_t size) {
        m_buffer.insert(m_buffer.end(), value, value + size);
        return *this;
    }

    const std::string& get() const {
        return m_buffer;
    }

private:
    std::string m_buffer;
};

// Encryptor
class Encryptor
{
public:
    Encryptor() {}
    void encrypt(const std::string& key, const Buffer& payload, std::string& cipher, std::vector<std::uint8_t>& iv) {
        cipher.clear();
        // generate iv
        using byte = unsigned char;
        CryptoPP::AutoSeededRandomPool rnd;
        rnd.GenerateBlock(iv.data(), iv.size());

        // decode key
        std::string decoded_key;
        CryptoPP::StringSource decoder(key, true, new CryptoPP::Base64Decoder(new CryptoPP::StringSink(decoded_key)));

        // encrypt payload to cipher
        CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption enc;
        enc.SetKeyWithIV(reinterpret_cast<const byte*>(decoded_key.c_str()), decoded_key.size(), iv.data());
        CryptoPP::StringSource s(payload.get(), true, new CryptoPP::StreamTransformationFilter(enc, new CryptoPP::StringSink(cipher)));
    }
};

// Payload
class Payload {
public:
    Payload(Simulator *sim) : m_sim(sim) {}
    void build(Buffer& payload) {
        payload.clear();
        // message types
        enum class Type : std::uint8_t { position = 1, speed = 2, attitude = 3, barometer = 4 };

        // position
        airmap::telemetry::Position position;
        position.set_timestamp(m_sim->getTimeStamp());
        position.set_latitude(m_sim->getLattitude());
        position.set_longitude(m_sim->getLongtitude());
        position.set_altitude_agl(m_sim->getAgl());
        position.set_altitude_msl(m_sim->getMsl());
        position.set_horizontal_accuracy(m_sim->getHorizAccuracy());
        auto messagePosition = position.SerializeAsString();
        payload.add<std::uint16_t>(htons(static_cast<std::uint16_t>(Type::position)))
            .add<std::uint16_t>(htons(messagePosition.size()))
            .add(messagePosition);

        // speed
        airmap::telemetry::Speed speed;
        speed.set_timestamp(m_sim->getTimeStamp());
        speed.set_velocity_x(m_sim->getVelocityX());
        speed.set_velocity_y(m_sim->getVelocityY());
        speed.set_velocity_z(m_sim->getVelocityZ());
        auto messageSpeed = speed.SerializeAsString();
        payload.add<std::uint16_t>(htons(static_cast<std::uint16_t>(Type::speed)))
            .add<std::uint16_t>(htons(messageSpeed.size()))
            .add(messageSpeed);

        // attitude
        airmap::telemetry::Attitude attitude;
        attitude.set_timestamp(m_sim->getTimeStamp());
        attitude.set_yaw(m_sim->getYaw());
        attitude.set_pitch(m_sim->getPitch());
        attitude.set_roll(m_sim->getRoll());
        auto messageAttitude = attitude.SerializeAsString();
        payload.add<std::uint16_t>(htons(static_cast<std::uint16_t>(Type::attitude)))
            .add<std::uint16_t>(htons(messageAttitude.size()))
            .add(messageAttitude);

        // barometer
        airmap::telemetry::Barometer barometer;
        barometer.set_timestamp(m_sim->getTimeStamp());
        barometer.set_pressure(m_sim->getPressure());
        auto messageBarometer = barometer.SerializeAsString();
        payload.add<std::uint16_t>(htons(static_cast<std::uint16_t>(Type::barometer)))
            .add<std::uint16_t>(htons(messageBarometer.size()))
            .add(messageBarometer);
    }

private:
    Simulator *m_sim;
};

// main
int main(int argc, char* argv[])
{
    // User ID
    const std::string userID = "{USER ID}";
    // API Key
    const std::string apiKey = "{API KEY}";
    // latitude
    const float latitude = 34.015802;
    // longitude
    const float longitude = -118.451303;
    // aes-256-cbc
    const uint8_t encryptionType = 1;

    // authenticate, create flight, and start communications
    Communicator communicator(apiKey);
    if (-1 == communicator.authenticate(userID)) {
        std::cout << "Authentication Failed!" << std::endl;
        return -1;
    }

    std::string flightID;
    if (-1 == communicator.create_flight(latitude, longitude, flightID)) {
        std::cout << "Flight Creation Failed!" << std::endl;
        return -1;
    }

    std::string key;
    if (-1 == communicator.start(flightID, key)) {
        std::cout << "Communication Failed!" << std::endl;
        return -1;
    }

    // hostname
    const char* hostname = "api-udp-telemetry.airmap.com";
    // port
    const int port = 16060;

    // connect to server
    UdpSender udp(hostname, port);
    if (-1 == udp.connect()) {
        std::cout << "Failed to connect!" << std::endl;
        return -1;
    }

    // serial number
    static std::uint32_t counter = 1;

    // prepare builder
    Simulator sim;
    Payload payloadBuilder(&sim);
    Buffer payload;

    // prepare encryptor
    Encryptor crypto;
    std::string cipher;
    std::vector<std::uint8_t> iv(16, 0);


    // send 100 packets @ 5 Hz
    for (int i = 0; i < 100; ++i) {
        // build payload
        payloadBuilder.build(payload);

        // encrypt payload
        crypto.encrypt(key, payload, cipher, iv);

        // build UDP packet
        Buffer packet;
        packet.add<std::uint32_t>(htonl(counter++))
            .add<std::uint8_t>(flightID.size())
            .add(flightID)
            .add<std::uint8_t>(encryptionType)
            .add(iv)
            .add(cipher);
        std::string data = packet.get();
        int length = static_cast<int>(data.size());

        // send packet
        if (udp.sendmsg(data.c_str(), length) == length)
            std::cout << "Telemetry packet sent successfully!" << std::endl;

        usleep(200000); // 5 Hz
    }

    // end communication and flight
    communicator.end(flightID);
    communicator.end_flight(flightID);

    return 0;
}

Sending Telemetry (UDP)