Sending Telemetry (gRPC)

Overview

The gRPC Telemetry Service provides an API for aircraft to self-report their position and other data points about their operation. These data points include geographic coordinates, altitude, velocity, battery level, connectivity status, barometric pressure, and other details about the vehicle.

This service is based on the gRPC protocol which streams data via HTTP/2 over TLS with Protobuf as the encoding format. For an introduction to gRPC and Protobuf, review the documentation on the grpc.io site.

๐Ÿšง

Note

Most client integrations should rely on one of the various platform SDKs that are available instead of integrating with the gRPC service directly. The SDKs provide a high level abstraction of the underlying gRPC API. This doc serves as a guide for integrations which have a need to integrate directly with the gRPC service itself on platforms not yet support by an SDK.

Generating gRPC Client Code

The .proto files provide rpc interfaces and a strictly-typed data model for interacting with the telemetry API. Depending on the development language used, these .proto files cannot be used directly. Instead, they are used to auto-generate client code for your respective development environment (e.g. Python, Java, C++, etc.)

๐Ÿ“˜

Download Interfaces

https://github.com/airmap/interfaces

The most common way to generate this code is by using the protoc command. As an example, the following Makefile will generate the necessary client code for a Go (golang) client using all of the .proto files.

PROTOS_DIR := ./grpc
PROTO_FILES := $(shell find $(PROTOS_DIR) -name '*.proto')
SOURCE_DIR := ./src

all: golang

golang:
    @mkdir -p $(SOURCE_DIR)/go
    @for x in $(PROTO_FILES); do \
        protoc -I$(PROTOS_DIR) --go_out=plugins=grpc,paths=source_relative:$(SOURCE_DIR)/go $$x; \
    done
make golang

For more information regarding the code generation process, please refer to the documentation specific to your development environment in the gRPC Tutorials Section.

API Endpoint

The AirMap Platform endpoint for gRPC telemetry is api.airmap.com:443.

Authentication

Sending telemetry on behalf of a flight requires that the gRPC client is properly authenticated. The client must authenticate as the same user that created the flight for which telemetry is being streamed.

For information about authenticating a user, please refer to the Authentication Documentation.

Upon authenticating a user, the user's JWT access token must be passed in the initial RPC's header metadata. The header authorization field must be set with a value of Bearer TOKEN, where TOKEN is the actual JWT access token.

A few examples of how to set the header follow with setup omitted for brevity.

token := "..."

md := metadata.New(map[string]string{
    "authorization": fmt.Sprintf("Bearer %s", token),
})

mdCtx := metadata.NewOutgoingContext(ctx, md)
stream, err := client.ConnectProvider(mdCtx)
const token = "..."

const metadata = new grpc.Metadata();
metadata.add("authorization", "Bearer " + token);

const stream = collector.ConnectProvider(metadata);

Service RPCs

The telemetry service is composed of one primary RPC which connects the client (known as a "provider") to the AirMap Platform telemetry collector.

// Collector exposes services that enable the reporting of telematic data.
service Collector {
  // ConnectProvider connects a stream of updates from a  provider to a collector
  rpc ConnectProvider(stream Update.FromProvider) returns (stream Update.ToProvider);
}

Upon executing this RPC, the client will stream Update.FromProvider and in return receive a stream of Update.ToProvider.

// Update wraps types used in the exchange of telemetry updates with a collector.
message Update {
  // FromProvider wraps messages being sent from a provider to a collector.
  message FromProvider {
    oneof details {
      system.Status status  = 1;  // Collector operational status.
      Report report         = 2;  // A report from a provider
    }
  }
  // ToProvider wraps messages being sent from a collector back to a provider.
  message ToProvider {
    oneof details {
      system.Status status  = 1;  // Collector operational status.
      system.Ack ack        = 2;  // Acknowledgement of received updates.
      system.Nack nack      = 3;  // A Negative acknowledgement of received updates.
    }
  }
}

Telemetry Reports

As part of the Update.FromProvider, the client will build a telemetry Report. A Report contains an instance of all telemetry associated with the aircraft at a given moment in time.

A telemetry report is structured with various sub-types, which describe spatial, atmospheric, or vehicle data points.

Each report is required to have a timestamp of when the report was observed, the aircraft or tracker identity, and the actual report details.

๐Ÿšง

The .protos in this documentation are provide for reference only. Always use to the most up-to-date and complete files hosted in the interfaces repo on GitHub.

// Report models a telemetry report at a given point in time.
message Report {
  google.protobuf.Timestamp observed    = 1;  // Time the report was observed; time of applicability.
  repeated tracking.Identity identities = 2;  // Identities associated with the report.

  // details is a discriminated union of all report types.
  oneof details {
    Spatial spatial       = 3;
    Vehicle vehicle       = 4;
    Atmosphere atmosphere = 5;
  }

  // Spatial models a spatial report
  message Spatial {
    measurements.Position position         = 1;  // A positional measurement. Required.
    measurements.Velocity velocity         = 2 [(grpc.nullable) = true];  // A velocity measurement.
    measurements.Orientation orientation   = 3 [(grpc.nullable) = true];  // An orientation/attitude measurement.
    measurements.Acceleration acceleration = 4 [(grpc.nullable) = true];  // An acceleration measurement.
  }

  // Vehicle models a vehicle report
  message Vehicle {
    repeated System systems            = 1;  // Detailed reports for vehicle systems.
    Endurance endurance                = 2 [(grpc.nullable) = true];  // The remaining endurance.
    google.protobuf.BoolValue airborne = 3 [(grpc.nullable) = true];  // A flag to indicate the vehicle is airborne.

    // System models a discrete vehicle system.
    message System {
      oneof details {
        Electrical electrical       = 1;
        Communication communication = 2;
        Propulsion propulsion       = 3;
        Safety safety               = 4;
      }

      // Electrical models the electrical system.
      message Electrical {
        repeated Bus buses         = 1 [(grpc.nullable) = true];
        repeated Battery batteries = 2 [(grpc.nullable) = true];

        // Bus models an electrical bus.
        message Bus {
          uint32 number         = 1 [(grpc.nullable) = true];  // The numeric identifier for the bus [0..n].
          units.Volts voltage   = 2 [(grpc.nullable) = true];  // The present voltage level in volts [V].
          units.Amperes current = 3 [(grpc.nullable) = true];  // The present load on the bus in amps. Positive values indicate a charge.
        }

        // Battery models a battery.
        message Battery {
          uint32 number              = 1 [(grpc.nullable) = true];  // The numeric identifier for the battery [0..n].
          units.Celsius temperature  = 2 [(grpc.nullable) = true];  // Temperature of battery in degrees Celsius[ยบC].
          units.Volts voltage        = 3 [(grpc.nullable) = true];  // The present voltage of the battery in volts[V].
          units.Amperes current      = 4 [(grpc.nullable) = true];  // The present load on the battery in amps. Positive values indicate a charge.
          units.AmpereHours capacity = 5 [(grpc.nullable) = true];  // Total amount of battery capacity in ampere-hours[Ah].
          units.Percent charge       = 6 [(grpc.nullable) = true];  // The remaining battery capacity as a % of capacity.
        }
      }

      // Propulsion models the propulsion system.
      message Propulsion {
        repeated Motor motors = 1;

        message Motor {
          uint32 number             = 1;  // The numeric identifier for the motor [0..n].
          units.Volts voltage       = 2 [(grpc.nullable) = true];  // The voltage of the motor in volts [V].
          units.Amperes current     = 3 [(grpc.nullable) = true];  // The current of the motor in Amps [A].
          units.Celsius temperature = 4 [(grpc.nullable) = true];  // The temperature of the motor in degrees[ยบC].
          units.RPM rpm             = 5 [(grpc.nullable) = true];  // The revolutions per minute (rpm) of the motor.
        }
      }

      // Communication models the communication system.
      message Communication {
        repeated Link links = 1;

        // Link models a communication link.
        message Link {
          oneof details {
            GPS gps           = 1;  // A GPS communication link.
            WiFi wifi         = 2;  // A Wi-Fi communication link.
            Cellular cellular = 3;  // A cellular communication link.
          }

          // WiFi models a Wi-Fi link.
          message WiFi {
            uint32 number  = 1 [(grpc.nullable) = true];  // The numeric identifier for the link [0..n].
            string ssid    = 2 [(grpc.nullable) = true];  // The network name.
            uint32 channel = 3 [(grpc.nullable) = true];  // The frequency channel.
            Band band      = 4 [(grpc.nullable) = true];  // The frequency band.
          }

          // GPS models a Global Positioning System link.
          message GPS {
            uint32 number     = 1 [(grpc.nullable) = true];  // The numeric identifier for the link [0..n].
            uint32 satellites = 2 [(grpc.nullable) = true];  // Number of satellites in range.
          }

          // Cellular models a cellular telecom link.
          message Cellular {
            uint32 number         = 1 [(grpc.nullable) = true];  // The numeric identifier for the link [0..n].
            units.Decibels signal = 2 [(grpc.nullable) = true];  // The signal level for the link in decibels [dB].
          }
        }

        // Band marks a radio frequency band.
        enum Band {
          BAND_UNKNOWN  = 0;
          BAND_2DOT4GHZ = 1;  // 2.4 GHz
          BAND_5DOT0GHZ = 2;  // 5.0 GHz
        }
      }

      // Safety models the safety system.
      message Safety {
        google.protobuf.BoolValue loss_of_link = 1 [(grpc.nullable) = true];  // Indicates the link to the aircraft has been lost.
      }
    }

    // Endurance models the run endurance of a vehicle.
    message Endurance {
      google.protobuf.Duration duration = 1 [(grpc.nullable) = true];  // The estimated remaining endurance expressed as duration.
      units.Meters distance             = 2 [(grpc.nullable) = true];  // The estimated remaining endurance expressed as distance.
    }
  }

  // Atmosphere models an atmospheric report
  message Atmosphere {
    measurements.Position position = 1;  // The position of the report.
    units.Pascals pressure         = 2 [(grpc.nullable) = true];  // An atmospheric pressure measurement [Pa].
    units.Celsius temperature      = 3 [(grpc.nullable) = true];  // A temperature measurement [ยบC].
    units.MetersPerSecond wind     = 4 [(grpc.nullable) = true];  // A wind measurement [m/s].
  }
}

Identities

The identities.proto file describes all the identities supported. For telemetry related to an AirMap flight, the Operation identity is required.

// Identity models a unique tracking identity.
message Identity {
  // details is a discriminated union of all identity types.
  oneof details {
    ProviderId provider_id     = 1;
    TrackId track_id           = 2;
    Callsign callsign          = 3;
    Registration registration  = 4;
    Operation operation        = 5;
    Icao icao                  = 6;
    Manufacturer manufacturer  = 7;
    NetworkInterface network   = 8;
    IMEI imei                  = 9;
    IMSI imsi                  = 10;
    Tracker tracker            = 11;
  }
// ...
}
// Operator models the unique id of an operator.
message Operator {
  string as_string = 1;  // The textual representation of the id.
}

// Operation models the unique id of an operation.
message Operation {
  string as_string = 1;  // The textual representation of the id.
}

// USS models the unique id of a UTM Service Supplier (USS)
message USS {
  string as_string = 1;  // The textual representation of the id.
}

// Tracker models the unique id of a tracker.
message Tracker {
  string as_string = 1;  // The textual representation of the id.
}

Sample Client

Below is a sample client that connects to the AirMap Telemetry Service and sends telemetry update

package telemetry

import (
    "context"
    "errors"
    "time"

    "github.com/airmap/interfaces/src/go/ids"
    "github.com/airmap/interfaces/src/go/measurements"
    "github.com/airmap/interfaces/src/go/telemetry"
    "github.com/airmap/interfaces/src/go/tracking"
    gunits "github.com/airmap/interfaces/src/go/units"
    "github.com/golang/protobuf/ptypes"

    md "google.golang.org/grpc/metadata"
)

// Runner models run of the telemetry simulation
type Runner struct {
    config Configuration
}

// Configuration wraps all configurable parameters
type Configuration struct {
    Client telemetry.CollectorClient
}

var (
    // SourceID identifies the source of traffic
    SourceID       = "simulation"
    authToken      = "test_token"
    headerMetadata = map[string]string{
        "Authorization": "Bearer " + authToken,
    }
)

// NewRunner creates a new simulation runner
func NewRunner(config Configuration) *Runner {
    return &Runner{config}
}

// Run runs a telemetry simulator
func (r *Runner) Run(ctx context.Context) error {
    var (
        reportTicker    = time.NewTicker(time.Second * 5)
        errCh           = make(chan error)
        sysReportTicker = time.NewTicker(time.Second * 10)
        newContext      = md.NewOutgoingContext(ctx, md.New(headerMetadata))
        identities      = []*tracking.Identity{
            {
                Details: &tracking.Identity_Imei{
                    Imei: &tracking.Identity_IMEI{
                        AsString: "fake_IMEI",
                    },
                },
            },
            {
                Details: &tracking.Identity_Operation_{
                    Operation: &tracking.Identity_Operation{
                        OperationId: &ids.Operation{
                            AsString: "fake_operation_id",
                        },
                        ServiceProviderId: &ids.USS{
                            AsString: "AIRMAP",
                        },
                    },
                },
            },
        }
    )

    stream, err := r.config.Client.ConnectProvider(newContext)
    if err != nil {
        return err
    }

    go func() {
        for {
            if _, err := stream.Recv(); err != nil {
                errCh <- err
                return
            }

            select {
            case <-newContext.Done():
                errCh <- newContext.Err()
                return
            default:
                // empty on purpose
            }
        }
    }()

    for {
        select {
        case _, ok := <-reportTicker.C:
            if !ok {
                return errors.New("failed to read from ticker channel")
            }

            rep := &telemetry.Report{
                Observed: ptypes.TimestampNow(),
                Details: &telemetry.Report_Spatial_{
                    Spatial: &telemetry.Report_Spatial{
                        Position: &measurements.Position{
                            Details: &measurements.Position_Absolute_{
                                Absolute: &measurements.Position_Absolute{
                                    Coordinate: &measurements.Coordinate2D{
                                        Longitude: &gunits.Degrees{
                                            Value: 180,
                                        },
                                        Latitude: &gunits.Degrees{
                                            Value: 180,
                                        },
                                    },
                                    Altitude: &measurements.Altitude{
                                        Height: &gunits.Meters{
                                            Value: 100,
                                        },
                                        Reference: measurements.Altitude_REFERENCE_ELLIPSOID,
                                    },
                                },
                            },
                        },
                        Velocity: &measurements.Velocity{
                            Details: &measurements.Velocity_Polar_{
                                Polar: &measurements.Velocity_Polar{
                                    Heading: &gunits.Degrees{
                                        Value: 60,
                                    },
                                    GroundSpeed: &gunits.MetersPerSecond{
                                        Value: 40,
                                    },
                                },
                            },
                        },
                    },
                },
            }
            rep.Identities = identities

            u := &telemetry.Update_FromProvider{
                Details: &telemetry.Update_FromProvider_Report{
                    Report: rep,
                },
            }

            if err := stream.Send(u); err != nil {
                return err
            }
        case _, ok := <-sysReportTicker.C:
            if !ok {
                return errors.New("failed to read from second ticket channel")
            }

            r := &telemetry.Report{
                Observed: ptypes.TimestampNow(),
                Details: &telemetry.Report_System_{
                    System: &telemetry.Report_System{
                        Endurance: ptypes.DurationProto(time.Minute * 30),
                    },
                },
            }
            r.Identities = identities

            if err := stream.Send(&telemetry.Update_FromProvider{
                Details: &telemetry.Update_FromProvider_Report{
                    Report: r,
                },
            }); err != nil {
                return err
            }
        case <-newContext.Done():
            return nil
        case err = <-errCh:
            return err
        }
    }
}

Updated 2 years ago

Sending Telemetry (gRPC)


Suggested Edits are limited on API Reference Pages

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