Sending Telemetry (UDP)

🚧

DEPRECATED

This UDP telemetry endpoint has been deprecated in favor of the gRPC telemetry service: Sending Telemetry (gRPC)

UDP Transport

Telemetry is sent in the form of packets (datagrams) with a specific encoding to the following host on port 16060 using 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.

PositionBytesTypeNameDescription
14uint32Serial NumberPacket Serial Number, starts at 1, increments by 1 per packet sent from client during this session (start-comm to end-comm)
21uint8Flight ID LengthThe length of the flight ID
31<x<256StringFlight IDThe flight ID
41uint8Encryption TypeAn ID for an AirMap supported encryption type (currently, only 'aes-256-cbc' is supported)
516rawInitialization VectorInitialization Vector used for aes-256-cbc encryption
61<x<64krawPayloadEncrypted 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.

PositionBytesTypeNameDescription
12uint16Message Type IDNumeric Message Type ID
22uint16Message LengthSerialized message length, for a max message size of 64kB
31<x<64xrawMessageSerialized 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 IDMessage
1Position
2Speed
3Attitude
4Barometer

1. Position

Geographical position of the aircraft

TypeNameDescription
uint64timestampUNIX time in milliseconds
float64latitudeThe recorded latitude
float 64longitudeThe recorded longitude
float32altitude_mslAltitude above mean sea level (ie. GPS), meters
float32altitude_aglAltitude above ground level, meters
float32horizontal_accuracyHorizontal 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

TypeNameDescription
uint64timestampUNIX time in milliseconds
float32velocity_xAircraft Speed in the x direction in meters per second using the North-East-Down (N-E-D) coordinate system
float32velocity_yAircraft Speed in the y direction in meters per second using the North-East-Down (N-E-D) coordinate system
float32velocity_zAircraft 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

TypeNameDescription
uint64timestampUNIX time in milliseconds
float32yawYaw angle measured from True North, { 0 <= x < 360 } degrees
float32pitchPitch angle, { -180 < x <= 180 } degrees
float32rollRoll angle, { -180 < x <= 180 } degrees

4. Barometer

Barometric pressure

TypeNameDescription
uint64timestampUNIX time in milliseconds
float32pressureBarometric pressure in hPa

Security

Payload encryption is supported with the following encryption types:

IDType
1aes-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;
}

Examples

The code samples below demonstrate applications that send telemetry via UDP in the following languages: Python, C++, and Java.

Python

In the Python example, 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 plan is filed and then submitted (with unique flight id returned) 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 telemetry.airmap.com on port 16060 and communications are ended with the /end-comm endpoint.

C++

The C++ example 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

Java

The Java example also shows an implementation of sending telemetry packets as outlined by previous sections of this document.

The example makes use of the following external libraries:

  • org.json
  • protobuf
import socket
import sys
import struct
import requests
import json
import jwt
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 int(d.microsecond/1000 + time.mktime(d.timetuple())*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:
        # note that anonymous login is rate limited.
        # cache token until its expiry and refetch once expired.
        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.text)["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 plan (returns planID)

def create_plan(api_key, token, pilotID):
    try:
        # update start_time and end_time below as appropriate
        response = requests.post(
            url="https://api.airmap.com/flight/v2/plan",
            headers={
                "X-API-Key": api_key,
                "Authorization": "Bearer "+token,
                "Content-Type": "application/json; charset=utf-8"
            },
            data=json.dumps({
                "takeoff_latitude": 33.85505651142062,
                "takeoff_longitude": -118.37099075317383,
                "pilot_id": pilotID,
                "start_time": "2020-04-23T18:38:44.730Z",
                "end_time": "2020-04-23T18:53:42.000Z",
                "max_altitude_agl": 100,
                "buffer": 1,
                "geometry": { "type": "Polygon", "coordinates": [ [ [ -118.37099075317383, 33.85505651142062 ], [ -118.37305068969727, 33.85502978214579 ], [ 
-118.37347984313963, 33.854673391015496 ], [ -118.37306141853333, 33.85231226221667 ], [ -118.37193489074707, 33.85174201755203 ], [ -118.36997151374815, 33.85176874785573 ], [ 
-118.36995005607605, 33.8528112231754 ], [ -118.37099075317383, 33.85505651142062 ] ] ] }
            })
        )
        if response.status_code == 200:
            return str(json.loads(response.text)["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

# submit plan (returns flightID)

def submit_plan(api_key, token, planID):
    try:
        response = requests.post(
            url="https://api.airmap.com/flight/v2/plan/"+planID+"/submit",
            headers={
                "X-API-Key": API_KEY,
                "Authorization": "Bearer "+token
            }
        )
        if response.status_code == 200:
            return str(json.loads(response.text)["data"]["flight_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.text)["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}"

# get token

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

# get pilot id

pilotID = jwt.decode(JWT, verify=False, algorithms=['HS256'])["sub"]
print("pilotID: "+pilotID)

# create flight plan

planID = create_plan(API_KEY, JWT, pilotID)
if planID == -1:
    print("Error creating plan")
    exit(1)
print("planID: "+planID)

# submit flight plan

flightID = submit_plan(API_KEY, JWT, planID)
if flightID == -1:
    print("Error creating flight")
    exit(1)
print("flightID: "+flightID)

# start comms

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

# decode key

secretKey = base64.b64decode(secretKey)

# messages

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

# addressing information of target
HOSTNAME = '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()

# serial number
counter = 1

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()

        attitude.timestamp              = timestamp
        attitude.yaw                    = sim.getYaw()
        attitude.pitch                  = sim.getPitch()
        attitude.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 = attitude.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
        def pad(data, BS):
            PS = (BS - len(data)) % BS
            if PS == 0:
                PS = BS
            P = (chr(PS) * PS).encode()
            return data + P

        payload = pad(payload, 16)
        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, counter, len(flightID), flightID.encode(), 1, IV, encryptedPayload)

        # send the payload
        s.send(PACKETDATA)

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

        # 5 Hz
        sleep(0.2)

except:
    print("Error sending telemetry")
    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)
#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 <nlohmann/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>

// command to build with g++
// g++ -std=c++1y -o "telemetry" telemetry.pb.cc telemetry.cpp -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 = "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;
}
import java.io.*;
import java.net.*;
import java.lang.*;
import java.security.*;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;

// http://www.java2s.com/Code/Jar/j/Downloadjavajsonjar.htm
import org.json.JSONObject;
// https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.4.0/
import airmap.telemetry.Telemetry;

/////////////////////////////////
// Commands to compile and run //
/////////////////////////////////

// javac -cp ".:./protobuf-java-3.4.0.jar:./json-20180130.jar" Example.java
// java -cp ".:./protobuf-java-3.4.0.jar:./json-20180130.jar": Example

public final class Example {
    
  // API Key
    public String API_KEY = "{API_KEY}";
  // User ID
  public String USER_ID = UUID.randomUUID().toString();
    // latitude
    public double latitude = 34.015802;
    // longitude
    public double longitude = -118.451303;
    // aes-256-cbc
  public int encryptionType = 1;
  // UDP server
  public String hostname = "telemetry.airmap.com";
  // UDP Port
  public int port = 16060;
   
    public class Simulator {
    
        Simulator() {
          init();
        }

    public long getTimeStamp() {
      return System.currentTimeMillis();
    }

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

    // simple sawtooth wave simulation
        private double BUMP (double val, double dt, double mx, double initval) {
            val += dt; 
            if (val > mx) val = initval; 
            return val;
        } 

        private 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;
        }

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

    public class Payload {
        Payload (Simulator sim) {
            m_sim = sim;
        }

        public byte[] build() {
      try {
        // position
        Telemetry.Position.Builder positionBuilder = Telemetry.Position.newBuilder();
        positionBuilder.setTimestamp(m_sim.getTimeStamp());
        positionBuilder.setLatitude(m_sim.getLattitude());
        positionBuilder.setLongitude(m_sim.getLongtitude());
        positionBuilder.setAltitudeAgl((float)m_sim.getAgl());
        positionBuilder.setAltitudeMsl((float)m_sim.getMsl());
        positionBuilder.setHorizontalAccuracy((float)m_sim.getHorizAccuracy());
        Telemetry.Position position = positionBuilder.build();

        // speed
        Telemetry.Speed.Builder speedBuilder = Telemetry.Speed.newBuilder();
        speedBuilder.setTimestamp(m_sim.getTimeStamp());
        speedBuilder.setVelocityX((float)m_sim.getVelocityX());
        speedBuilder.setVelocityY((float)m_sim.getVelocityX());
        speedBuilder.setVelocityZ((float)m_sim.getVelocityX());
        Telemetry.Speed speed = speedBuilder.build();

        // attitude
        Telemetry.Attitude.Builder attitudeBuilder = Telemetry.Attitude.newBuilder();
        attitudeBuilder.setTimestamp(m_sim.getTimeStamp());
        attitudeBuilder.setYaw((float)m_sim.getPitch());
        attitudeBuilder.setPitch((float)m_sim.getYaw());
        attitudeBuilder.setRoll((float)m_sim.getRoll());
        Telemetry.Attitude attitude = attitudeBuilder.build();

        // barometer
        Telemetry.Barometer.Builder barometerBuilder = Telemetry.Barometer.newBuilder();
        barometerBuilder.setTimestamp(m_sim.getTimeStamp());
        barometerBuilder.setPressure((float)m_sim.getPressure());
        Telemetry.Barometer barometer = barometerBuilder.build();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream daos = new DataOutputStream(baos);

        // write position to payload
        daos.writeShort(1);
        daos.writeShort(position.getSerializedSize());
        daos.write(position.toByteArray());

        // write speed to payload
        daos.writeShort(2);
        daos.writeShort(speed.getSerializedSize());
        daos.write(speed.toByteArray());

        // write attitude to payload
        daos.writeShort(3);
        daos.writeShort(attitude.getSerializedSize());
        daos.write(attitude.toByteArray());

        // write barometer to payload
        daos.writeShort(4);
        daos.writeShort(barometer.getSerializedSize());
        daos.write(barometer.toByteArray());

        daos.close();

        byte[] payload = baos.toByteArray();
        baos.close();

        return payload;

      } catch(Exception e) {
        e.printStackTrace();
        byte payload[] = {};
        return payload;
      }
        }

        private Simulator m_sim;
    }    

  public class Communicator {
    Communicator () {}

    public String getToken(String userID) throws Exception {
        
      String headers[][] = {};
      String data[][] = {{"user_id", USER_ID}};
      String ss = post(m_url+"/auth/v1/anonymous/token", headers, data);
      System.out.println(ss);
      if (ss != null) {
        JSONObject obj = new JSONObject(ss);
        String token = obj.getJSONObject("data").getString("id_token");
        return token;
      } else {
        return "";
      }

    }
      
    public String createFlight(double latitude, double longitude, String token) throws Exception {
        
      String headers[][] = {{"Authorization", "Bearer "+token}};
      String data[][] = {{"latitude", String.valueOf(latitude)}, {"longitude", String.valueOf(longitude)}};
      String ss = post(m_url+"/flight/v2/point", headers, data);
      System.out.println(ss);
      if (ss != null) {
          JSONObject obj = new JSONObject(ss);
        String flightID = obj.getJSONObject("data").getString("id");
        return flightID;
      } else {
        return "";
      }

    }

    public String startComms(String flightID, String token) throws Exception {
        
      String headers[][] = {{"Authorization", "Bearer "+token}};
      String data[][] = {};
      String ss = post(m_url+"/flight/v2/"+flightID+"/start-comm", headers, data);
      System.out.println(ss);
      if (ss != null) {
        JSONObject obj = new JSONObject(ss);
        String key = obj.getJSONObject("data").getString("key");
        return key;
      } else {
        return "";
      }

    }
      
    public void endComms(String flightID, String token) throws Exception {
        
      String headers[][] = {{"Authorization", "Bearer "+token}};
      String data[][] = {};
      String ss = post(m_url+"/flight/v2/"+flightID+"/end-comm", headers, data);
      System.out.println(ss);

    }

    public void endFlight(String flightID, String token) throws Exception {
        
      String headers[][] = {{"Authorization", "Bearer "+token}};
      String data[][] = {};
      String ss = post(m_url+"/flight/v2/"+flightID+"/end", headers, data);
      System.out.println(ss);

    }

    private String post(String target, String headers[][], String data[][]) throws Exception {
        
      URL url = new URL(target);
      HttpURLConnection con = (HttpURLConnection) url.openConnection();
      con.setRequestMethod("POST");
      
      // Add request header
      con.setRequestProperty("X-API-Key", API_KEY);
      con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
      con.setRequestProperty("Content-Type","application/json");
      for (int i=0; i<headers.length; i++) {
        con.setRequestProperty(headers[i][0], headers[i][1]);
      }

      String postJsonData = "{";
      for (int i=0; i<data.length; i++) {
        try {
          Double.parseDouble(data[i][1]);
          postJsonData += String.format("\"%s\":%s", data[i][0], data[i][1]);
        } catch (NumberFormatException e) {
          postJsonData += String.format("\"%s\":\"%s\"", data[i][0], data[i][1]);
        }
        if (i<data.length-1)
          postJsonData +=",";
      }
      postJsonData += "}";

      System.out.println(postJsonData);
      // Send post request
      con.setDoOutput(true);
      DataOutputStream wr = new DataOutputStream(con.getOutputStream());
      wr.writeBytes(postJsonData);
      wr.flush();
      wr.close();
      
      // Get response
      int rc = con.getResponseCode();
      if(rc == 200) {
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String output;
        StringBuffer response = new StringBuffer();
        while ((output = in.readLine()) != null) {
          response.append(output);
        }
        in.close();
        return response.toString();
      }
      else {
        return null;   
      }
    }

    private String m_url = "https://api.airmap.com";
  }

    public class UDPSender {
    UDPSender() {}

    public void connect() {
      try {     
        // Get the internet address of the specified host
        address = InetAddress.getByName(hostname);

        // Create a datagram socket, send the packet through it, close it.
        dsocket = new DatagramSocket();
      } catch (Exception e) {
        System.err.println(e);
      }
    }

    public void sendmsg(byte[] message) {
      try {
        // Initialize a datagram packet with data and address
        DatagramPacket packet = new DatagramPacket(message, message.length, address, port);
        dsocket.send(packet);
      } catch (Exception e) {
        System.err.println(e);
      }
    }

    public void close() {
      dsocket.close();
    }

    private DatagramSocket dsocket;
    private InetAddress address;

  }   
 
    public class Encryptor {
    Encryptor() {}
    // Encrypts a SecretMessage with the given key and iv
    public byte[] encrypt(byte[] key, IvParameterSpec iv, byte[] payload) throws InvalidAlgorithmParameterException, InvalidKeyException, IOException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
      SecretKey secretKey = new SecretKeySpec(key, "AES");
      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
      cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
      return cipher.doFinal(payload);
    }

    // Generates a random IV to be used for the AES encryption
    public IvParameterSpec generateIV() {
      int blockSize = 16;
      byte[] iv = new byte[blockSize];
      new SecureRandom().nextBytes(iv);
      return new IvParameterSpec(iv);
    }
  }  

    public void run() throws Exception {
        
    Communicator communicator = new Communicator();

        String token = communicator.getToken(USER_ID);
    String flightID = communicator.createFlight(latitude, longitude, token);
    String key = communicator.startComms(flightID, token);

    UDPSender udp = new UDPSender();
    udp.connect();

    // Packet sequence number
    int counter = 1;

    Simulator simulator = new Simulator();
    Payload payloadBuilder = new Payload(simulator);
    Encryptor encryptor = new Encryptor();

    // send 100 packets @ 5 Hz
    for (int i = 0; i < 100; ++i) {
      // build UDP packet
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      DataOutputStream daos = new DataOutputStream(baos);

      daos.writeInt(counter);
      daos.writeByte((byte)flightID.length());
      daos.writeBytes(flightID);

      IvParameterSpec iv = encryptor.generateIV();
      daos.writeByte(1);
      daos.write(iv.getIV());

      // build payload
      byte[] payload = payloadBuilder.build();
      ByteArrayOutputStream payloadBaos = new ByteArrayOutputStream();
      payloadBaos.write(payload);

      byte[] encryptedPayload = encryptor.encrypt(Base64.getDecoder().decode(key), iv, payloadBaos.toByteArray());
      payloadBaos.close();

      daos.write(encryptedPayload);
      daos.close();

      byte[] packet = baos.toByteArray();
      baos.close();

      // send packet
      udp.sendmsg(packet);
      System.out.println("Sent packet!");

      Thread.sleep(200); // 5 Hz
    }

    // close socket, end comms, and end flight
    udp.close();
    communicator.endComms(flightID, token);
    communicator.endFlight(flightID, token);

    }

    public static void main(String args[]) throws Exception {
        
        try {
            Example example = new Example();
            example.run();
        } catch (Exception e) {
            e.printStackTrace();
     }
            
    }
}

Updated 2 years ago

Sending Telemetry (UDP)


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.