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.
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 | Attitude |
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_agl | 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;
}
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