wingbits client and cli helpers for mgw310 and other devices

This commit is contained in:
2026-06-22 22:11:27 -04:00
parent edb6b39543
commit 24fe4258f7
28 changed files with 2980 additions and 2 deletions
+29
View File
@@ -0,0 +1,29 @@
name: Publish
on:
push:
tags: ["v*"]
env:
GO_GIT_HOST: gitea.libretechconsulting.com
jobs:
release:
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') # Only run on tag push
steps:
- name: Set up Go Environment
uses: actions/setup-go@v4
with:
go-version: '1.26'
- name: Run Go List
continue-on-error: true
env:
TAG_NAME: ${{ github.ref_name }} # Use the pushed tag name
run: |
if [[ "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
GOPROXY=proxy.golang.org go list -m ${GO_GIT_HOST}/${GITHUB_REPOSITORY}@$TAG_NAME
else
echo "Error: Invalid tag format '$TAG_NAME'. Expected 'vX.X.X'."
exit 1
fi
+64
View File
@@ -0,0 +1,64 @@
# AGENTS.md
Project-level instructions for working in this repository. Read this before
touching the filesystem.
## What this is
Go helper packages for talking to a [Wingbits](https://wingbits.com) ADS-B
station (e.g. the MGW310) and decoding the readsb / tar1090 JSON files it serves.
Module: `gitea.libretechconsulting.com/rmcguire/wingbits` (Go 1.26).
## Layout
```
pkg/types/readsb # decode types for aircraft/receiver/stats/outline + aircraft filters
pkg/types/wingbits # decode types for diagnostics/metrics/status (+ Prometheus text parser)
pkg/client # HTTP Client: query + stream every endpoint, returns the types above
cmd/wingbits # flag-driven CLI for one-off queries (test/utility tool)
```
Rules of the split:
- `pkg/types/*` is **decode-only**. No HTTP, no I/O beyond `encoding/json` and the
Prometheus text parser. These packages must not import `pkg/client`.
- `pkg/client` owns all transport/request logic and depends on both type packages.
- Keep the two type packages independent of each other.
## Station topology (important for endpoint routing)
A station exposes two HTTP roots; both are plaintext HTTP on the test unit:
| Root | Default port | Serves |
|-----------|--------------|---------------------------------------------------------------|
| Wingbits | `:8088` | `/network/diagnostics`, `/metrics`, `/tailscale/status`, and readsb files under `/readsb/` |
| tar1090 | `:8504` | readsb files under `/data/`, plus `/data/outline.json` |
`outline.json` is **only** served by the tar1090 root. When a tar1090 endpoint is
configured it overrides the Wingbits root for all readsb files.
## Testing
```
go test ./...
```
Tests decode captured fixtures in each package's `testdata/` directory and drive
the client against an `httptest` server. `pkg/client` reuses the fixtures from
`pkg/types/*/testdata` (a single source of truth — do not duplicate them).
The live reference unit is an **MGW310 at `192.168.0.127`** (also
`miner.mcguire.local`), plaintext HTTP on all ports. Re-capture fixtures from it
with `curl` when fields change; never hand-edit `testdata/` payloads.
## Coding standards
- Go only. Keep functions within ~25 lines; decompose helpers as needed.
- Every package has a package comment (Go convention) in one of its files.
- Comment only the non-obvious. Field docs on the decode structs are intentional
— they capture readsb/Wingbits semantics that aren't evident from the name.
- The known JSON quirks to preserve when editing decode types:
- `alt_baro` may be the string `"ground"` instead of a number → `readsb.AltBaro`.
- readsb timestamps are fractional-second Unix floats → `readsb.UnixTime`.
- `outline.json` points are `[lat, lon, altFeet]` tuples → `readsb.RangePoint`.
+280 -2
View File
@@ -1,3 +1,281 @@
# Wingbits Packages
# wingbits
Helper packages for interacting with wingbits API / readsb json files
Typed Go client and decoders for [Wingbits](https://wingbits.com) ADS-B
stations (such as the **MGW310**) and the underlying
[readsb](https://github.com/wiedehopf/readsb) / tar1090 JSON feeds.
Point it at a station and get clean Go structs for the aircraft your antenna is
tracking, the receiver's range, decoder statistics, network diagnostics, feeder
telemetry, and Tailscale status — as oneshot queries **or** live streams over a
channel.
```go
c, _ := client.NewMGW310Client("192.168.0.127")
// Every airliner above FL300 that we currently have a position for:
report, _ := c.Aircraft(ctx, readsb.WithPosition(), readsb.MinAltitude(30000))
for _, a := range report.Aircraft {
fmt.Printf("%s %-8s %5dft %3.0fnm\n", a.Hex, a.Callsign(), a.AltBaro.Feet, a.RDst)
}
// a9d59a UAL2350 37000ft 186nm
// ...171 more
```
---
## Why
A Wingbits station already publishes a rich set of JSON endpoints — but every
project ends up rewriting the same brittle structs, rediscovering the same
quirks (`"alt_baro": "ground"`, fractionalsecond Unix timestamps, `[lat, lon,
alt]` tuples), and handrolling another polling loop. This module does that once,
correctly, with field documentation drawn straight from
[readsb's `README-json.md`](https://github.com/wiedehopf/readsb/blob/dev/README-json.md)
and validated against real hardware.
- **Typed, documented decoders** for every endpoint — no `map[string]any`.
- **One client, both roots.** The readsb files and the Wingbitsspecific
endpoints are reachable through a single `Client`, which knows that readsb
lives under `/readsb/` on `:8088` and under `/data/` on the tar1090 root `:8504`.
- **Composable aircraft filters** — position, altitude, range, squawk, emergency,
callsign, category, MLAT, signal strength, and more, AND/ORcomposable.
- **Streaming built in.** Any endpoint becomes a `<-chan Update[T]` with a
configurable poll interval and buffer.
- **Bring your own everything** — `context.Context` on every call, pluggable
`*http.Client`, perendpoint TLS, custom ports/schemes/paths.
- **Zero nonstdlib dependencies**, including a small Prometheus text parser for
`/metrics`.
## Install
```sh
go get gitea.libretechconsulting.com/rmcguire/wingbits
```
```go
import (
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/client"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/readsb"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/wingbits"
)
```
## Packages
| Package | Import | Responsibility |
|---|---|---|
| `client` | `…/wingbits/pkg/client` | HTTP client — query and stream every endpoint |
| `readsb` | `…/wingbits/pkg/types/readsb` | Decode types + filters for `aircraft`, `receiver`, `stats`, `outline` |
| `wingbits` | `…/wingbits/pkg/types/wingbits` | Decode types for `diagnostics`, `metrics`, `status` |
The `types/*` packages are decodeonly and have no I/O — useful on their own if
you already have the JSON (from a file, a message bus, a snapshot) and just want
to unmarshal it.
## Endpoints at a glance
A station serves two HTTP roots. The client routes each call automatically.
| Method | Source file | Default location |
|---|---|---|
| `Aircraft` | `aircraft.json` | `:8504/data/` → falls back to `:8088/readsb/` |
| `Receiver` | `receiver.json` | `:8504/data/``:8088/readsb/` |
| `Stats` | `stats.json` | `:8504/data/``:8088/readsb/` |
| `Outline` | `outline.json` | `:8504/data/` *(tar1090 only)* |
| `Diagnostics` | `/network/diagnostics` | `:8088` |
| `Metrics` | `/metrics` | `:8088` |
| `Status` | `/tailscale/status` | `:8088` |
When a tar1090 endpoint is configured it **overrides** the Wingbits root for the
readsb files; `outline.json` is served exclusively by tar1090.
## Quickstart
```go
package main
import (
"context"
"fmt"
"time"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/client"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/readsb"
)
func main() {
// Sensible MGW310 defaults: Wingbits on :8088, tar1090 on :8504.
c, err := client.NewMGW310Client("192.168.0.127")
if err != nil {
panic(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
report, err := c.Aircraft(ctx, readsb.WithPosition())
if err != nil {
panic(err)
}
fmt.Printf("tracking %d aircraft with a position\n", len(report.Aircraft))
}
```
### Filtering aircraft
Filters are plain predicates; pass any number and they compose with AND. They
work both on a fetched report (`report.Filter(...)`) and inline on the query.
```go
// Anything squawking an emergency, anywhere:
emerg, _ := c.Aircraft(ctx, readsb.InEmergency())
// Low and close: under 10,000 ft and within 30 nm, strong signal:
local, _ := c.Aircraft(ctx,
readsb.WithPosition(),
readsb.MaxAltitude(10000),
readsb.WithinNM(30),
readsb.MinRSSI(-24),
)
// OR-composition and negation:
heavies, _ := c.Aircraft(ctx,
readsb.Any(readsb.WithCategory("A5"), readsb.WithType("mlat")),
readsb.Not(readsb.OnGround()),
)
```
Available filters include `WithPosition`, `WithHex`, `WithCallsign`,
`WithSquawk`, `WithCategory`, `WithType`, `InEmergency`, `IsMLAT`,
`MinAltitude`, `MaxAltitude`, `OnGround`, `WithinNM`, `SeenWithin`, `MinRSSI`,
plus the `Not` and `Any` combinators.
### Streaming
Every endpoint has a `Stream*` variant. It emits immediately, then on each tick,
and closes the channel when the context is cancelled. Each `Update[T]` carries
either a value or the error from that poll.
```go
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for u := range c.StreamAircraft(ctx, 2*time.Second, readsb.WithPosition()) {
if u.Err != nil {
log.Printf("poll failed: %v", u.Err)
continue
}
log.Printf("%d aircraft in view", len(u.Value.Aircraft))
}
```
Set the channel buffer with `client.WithStreamBufferSize(n)`.
### Station health & feeder telemetry
```go
diag, _ := c.Diagnostics(ctx)
fmt.Println("backend reachable:", diag.AllReachable())
m, _ := c.Metrics(ctx)
wb := m.Wingbits() // typed view of the wingbits_* families
fmt.Printf("feeder %s — NATS connected=%v, %0.f msgs sent\n",
wb.Version, wb.NATS.Connected, wb.NATS.Sent)
// Raw access to any Prometheus family is available too:
if v, ok := m.Value("wingbits_beast_received_total"); ok {
fmt.Printf("beast messages received: %.0f\n", v)
}
```
## CLI
A small flag-driven tool lives under `cmd/wingbits` for one-off queries. It
prints any endpoint as indented JSON (pipe it to `jq`) and can stream on an
interval.
Install it onto your `PATH` (lands in `$(go env GOPATH)/bin`, e.g. `~/go/bin`):
```sh
go install gitea.libretechconsulting.com/rmcguire/wingbits/cmd/wingbits@latest
```
Then run it directly:
```sh
wingbits -host 192.168.0.127 check
wingbits -host 192.168.0.127 aircraft -with-pos -min-alt 30000
wingbits -host 192.168.0.127 -interval 2s stats
wingbits -host 192.168.0.127 metrics | jq .NATS
```
Or run it from a checkout without installing, via `go run ./cmd/wingbits …`.
The `check` command probes every endpoint once and reports reachability with a
latency and a content summary, exiting non-zero if any fail:
```
[✓] aircraft 78ms 248 aircraft
[✓] receiver 11ms readsb 3.14.1682
[✓] stats 10ms 91593113 msgs, 230 with pos
[✓] outline 24ms 360 range points
[✓] diagnostics 1.652s 3 interfaces, backend reachable=true
[✓] metrics 24ms feeder v1.12.1, NATS connected=true
[✓] status 12ms state=Stopped online=false
```
Commands: `aircraft`, `receiver`, `stats`, `outline`, `diagnostics`, `metrics`,
`status`, `check`. The `aircraft` command accepts filter flags (`-with-pos`,
`-emergency`, `-min-alt`, `-max-alt`, `-within-nm`, `-squawk`, `-callsign`); run
with no command for full usage.
## Configuration
`NewMGW310Client(host, opts...)` wires the standard MGW310 ports and then applies
your options, so you can override anything. For nonstandard deployments build it
explicitly with `New`:
```go
c, err := client.New(
client.WithWingbitsEndpoint(client.Endpoint{
Scheme: "https", Host: "station.example", Port: 8443,
TLS: &tls.Config{ /* ... */ },
}),
client.WithTar1090Endpoint(client.Endpoint{
Host: "station.example", Port: 8504, DataPath: "data",
}),
client.WithHTTPClient(myHTTPClient), // optional; TLS above is ignored if set
client.WithStreamBufferSize(8),
client.WithUserAgent("my-app/1.0"),
)
```
Omit `WithTar1090Endpoint` to serve the readsb files from the Wingbits root's
`/readsb/` path instead (note: `Outline` then returns an error, since
`outline.json` is tar1090only).
## Handled quirks
These are decoded for you, so you never see them as surprises:
- **`alt_baro: "ground"`** — barometric altitude is a number *or* the string
`"ground"`. Decoded into `readsb.AltBaro{Feet, OnGround}`.
- **Fractional Unix timestamps** — `now`, window `start`/`end` are float seconds.
Decoded into `readsb.UnixTime` (embeds `time.Time`).
- **Range tuples** — `outline.json` points are `[lat, lon, altFeet]` arrays.
Decoded into `readsb.RangePoint`.
- **Stale positions** — when a position ages out it moves to `lastPosition`;
exposed as `*readsb.Position`.
## Status
Validated endtoend against a live MGW310. Decoders are covered by tests against
captured fixtures; the client is covered against an inprocess test server.
```sh
go test ./...
```
## License
See repository.
+82
View File
@@ -0,0 +1,82 @@
package main
import (
"flag"
"fmt"
"os"
"time"
)
// config holds the parsed command-line options.
type config struct {
cmd string
host string
interval time.Duration
timeout time.Duration
// aircraft filters
withPos bool
emergency bool
minAlt int
maxAlt int
withinNM float64
squawk string
callsign string
}
// parseFlags parses global flags, the command, and then command-specific flags.
// Aircraft filters are only accepted for the aircraft command.
func parseFlags() config {
var cfg config
fs := flag.NewFlagSet("wingbits", flag.ExitOnError)
fs.Usage = usage(fs)
fs.StringVar(&cfg.host, "host", "", "station host or IP (required)")
fs.DurationVar(&cfg.interval, "interval", 0, "if >0, stream on this interval")
fs.DurationVar(&cfg.timeout, "timeout", 10*time.Second, "per-request timeout")
_ = fs.Parse(os.Args[1:])
cfg.cmd = fs.Arg(0)
requireHostAndCmd(fs, cfg)
parseAircraftFlags(&cfg, fs.Args()[1:])
return cfg
}
func requireHostAndCmd(fs *flag.FlagSet, cfg config) {
if cfg.host == "" || cfg.cmd == "" {
fs.Usage()
os.Exit(2)
}
}
// parseAircraftFlags consumes filter flags that follow the command word.
func parseAircraftFlags(cfg *config, args []string) {
fs := flag.NewFlagSet(cfg.cmd, flag.ExitOnError)
fs.BoolVar(&cfg.withPos, "with-pos", false, "[aircraft] only aircraft with a position")
fs.BoolVar(&cfg.emergency, "emergency", false, "[aircraft] only aircraft squawking an emergency")
fs.IntVar(&cfg.minAlt, "min-alt", 0, "[aircraft] minimum barometric altitude (ft)")
fs.IntVar(&cfg.maxAlt, "max-alt", 0, "[aircraft] maximum barometric altitude (ft)")
fs.Float64Var(&cfg.withinNM, "within-nm", 0, "[aircraft] within this range of the receiver (nm)")
fs.StringVar(&cfg.squawk, "squawk", "", "[aircraft] match this Mode A squawk code")
fs.StringVar(&cfg.callsign, "callsign", "", "[aircraft] match this callsign")
_ = fs.Parse(args)
}
func usage(fs *flag.FlagSet) func() {
return func() {
out := fs.Output()
fmt.Fprintf(out, "wingbits — query a Wingbits station\n\n")
fmt.Fprintf(out, "Usage:\n wingbits -host HOST [global flags] COMMAND [command flags]\n\n")
fmt.Fprintf(out, "Commands:\n")
for _, c := range commandDescs {
fmt.Fprintf(out, " %-12s %s\n", c.name, c.desc)
}
fmt.Fprintf(out, "\nGlobal flags:\n")
fs.PrintDefaults()
fmt.Fprintf(out, "\naircraft flags: -with-pos -emergency -min-alt -max-alt -within-nm -squawk -callsign\n")
fmt.Fprintf(out, "\nExamples:\n")
fmt.Fprintf(out, " wingbits -host 192.168.0.127 check\n")
fmt.Fprintf(out, " wingbits -host 192.168.0.127 aircraft -with-pos -min-alt 30000\n")
fmt.Fprintf(out, " wingbits -host 192.168.0.127 -interval 2s stats\n")
fmt.Fprintf(out, " wingbits -host 192.168.0.127 metrics | jq .NATS\n")
}
}
+217
View File
@@ -0,0 +1,217 @@
// Command wingbits is a small flag-driven CLI for one-off queries against a
// Wingbits station. It prints any endpoint as indented JSON (pipe it to jq), and
// can stream on an interval. It is a thin convenience wrapper over pkg/client.
//
// wingbits -host 192.168.0.127 aircraft -with-pos -min-alt 30000
// wingbits -host 192.168.0.127 -interval 2s stats
// wingbits -host 192.168.0.127 metrics | jq .NATS
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"os/signal"
"strings"
"time"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/client"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/readsb"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/wingbits"
)
// query fetches one endpoint's decoded value.
type query func(context.Context) (any, error)
func main() {
cfg := parseFlags()
c, err := client.NewMGW310Client(cfg.host,
client.WithUserAgent("wingbits-cli/1.0"))
if err != nil {
fatal(err)
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
if err := run(ctx, c, cfg); err != nil {
fatal(err)
}
}
func run(ctx context.Context, c *client.Client, cfg config) error {
if cfg.cmd == "check" {
return runChecks(ctx, c, cfg)
}
q, ok := queries(c, buildFilters(cfg))[cfg.cmd]
if !ok {
return fmt.Errorf("unknown command %q; want one of: %s", cfg.cmd, commandNames())
}
if cfg.interval <= 0 {
return printOnce(ctx, cfg.timeout, q)
}
return pollLoop(ctx, cfg, q)
}
// queries maps each command name to its fetch. aircraft applies the filters;
// metrics is reduced to the typed Wingbits feeder view, which is the useful bit.
func queries(c *client.Client, filters []readsb.AircraftFilter) map[string]query {
return map[string]query{
"aircraft": func(ctx context.Context) (any, error) { return c.Aircraft(ctx, filters...) },
"receiver": func(ctx context.Context) (any, error) { return c.Receiver(ctx) },
"stats": func(ctx context.Context) (any, error) { return c.Stats(ctx) },
"outline": func(ctx context.Context) (any, error) { return c.Outline(ctx) },
"diagnostics": func(ctx context.Context) (any, error) { return c.Diagnostics(ctx) },
"status": func(ctx context.Context) (any, error) { return c.Status(ctx) },
"metrics": metricsQuery(c),
}
}
func metricsQuery(c *client.Client) query {
return func(ctx context.Context) (any, error) {
m, err := c.Metrics(ctx)
if err != nil {
return nil, err
}
return m.Wingbits(), nil
}
}
// commandDescs lists every command with a one-line description, in display order.
var commandDescs = []struct{ name, desc string }{
{"aircraft", "tracked aircraft (aircraft.json) — supports filters"},
{"receiver", "receiver location & capabilities (receiver.json)"},
{"stats", "decoder statistics (stats.json)"},
{"outline", "receiver range polygon (outline.json)"},
{"diagnostics", "network diagnostics (/network/diagnostics)"},
{"metrics", "feeder telemetry (/metrics)"},
{"status", "Tailscale status (/tailscale/status)"},
{"check", "probe every endpoint and report reachability"},
}
// checkOrder is the order endpoints are probed by the check command.
var checkOrder = []string{
"aircraft", "receiver", "stats", "outline", "diagnostics", "metrics", "status",
}
const (
greenCheck = "[\033[32m✓\033[0m]"
redCross = "[\033[31m✗\033[0m]"
)
// runChecks probes every endpoint once, printing a pass/fail line each, and
// returns a non-nil error (non-zero exit) if any endpoint failed.
func runChecks(ctx context.Context, c *client.Client, cfg config) error {
qs := queries(c, nil)
var failed int
for _, name := range checkOrder {
if !checkOne(ctx, cfg.timeout, name, qs[name]) {
failed++
}
}
if failed > 0 {
return fmt.Errorf("%d/%d endpoints failed", failed, len(checkOrder))
}
return nil
}
// checkOne probes a single endpoint and prints its status, latency and a short
// content summary. It returns true on success.
func checkOne(ctx context.Context, timeout time.Duration, name string, q query) bool {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
start := time.Now()
v, err := q(ctx)
elapsed := time.Since(start).Round(time.Millisecond)
if err != nil {
fmt.Printf("%s %-12s %7s %v\n", redCross, name, elapsed, err)
return false
}
fmt.Printf("%s %-12s %7s %s\n", greenCheck, name, elapsed, summarize(v))
return true
}
// summarize returns a one-line description of a decoded endpoint result, used to
// show that an endpoint is serving real content and not just 200-ing.
func summarize(v any) string {
switch t := v.(type) {
case *readsb.AircraftReport:
return fmt.Sprintf("%d aircraft", len(t.Aircraft))
case *readsb.Receiver:
return fmt.Sprintf("readsb %s", strings.Fields(t.Version)[0])
case *readsb.Stats:
return fmt.Sprintf("%d msgs, %d with pos", t.Total.Messages, t.AircraftWithPos)
case *readsb.Outline:
return fmt.Sprintf("%d range points", len(t.ActualRange.Last24h.Points))
case *wingbits.Diagnostics:
return fmt.Sprintf("%d interfaces, backend reachable=%v", len(t.Interfaces), t.AllReachable())
case wingbits.WingbitsMetrics:
return fmt.Sprintf("feeder %s, NATS connected=%v", t.Version, t.NATS.Connected)
case *wingbits.TailscaleStatus:
return fmt.Sprintf("tailscale state=%s online=%v", t.State, t.Online)
}
return "ok"
}
// printOnce runs a single query under a timeout and prints the result as JSON.
func printOnce(ctx context.Context, timeout time.Duration, q query) error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
v, err := q(ctx)
if err != nil {
return err
}
return printJSON(v)
}
// pollLoop prints a fresh result immediately and then once per interval until
// the context is cancelled (Ctrl-C). Per-poll errors are logged, not fatal.
func pollLoop(ctx context.Context, cfg config, q query) error {
tick := time.NewTicker(cfg.interval)
defer tick.Stop()
for {
if err := printOnce(ctx, cfg.timeout, q); err != nil && ctx.Err() == nil {
fmt.Fprintln(os.Stderr, "wingbits:", err)
}
select {
case <-ctx.Done():
return nil
case <-tick.C:
}
}
}
func printJSON(v any) error {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(v)
}
func buildFilters(cfg config) []readsb.AircraftFilter {
var f []readsb.AircraftFilter
add := func(ok bool, flt readsb.AircraftFilter) {
if ok {
f = append(f, flt)
}
}
add(cfg.withPos, readsb.WithPosition())
add(cfg.minAlt > 0, readsb.MinAltitude(cfg.minAlt))
add(cfg.maxAlt > 0, readsb.MaxAltitude(cfg.maxAlt))
add(cfg.withinNM > 0, readsb.WithinNM(cfg.withinNM))
add(cfg.squawk != "", readsb.WithSquawk(cfg.squawk))
add(cfg.callsign != "", readsb.WithCallsign(cfg.callsign))
add(cfg.emergency, readsb.InEmergency())
return f
}
func fatal(err error) {
fmt.Fprintln(os.Stderr, "wingbits:", err)
os.Exit(1)
}
func commandNames() string {
names := make([]string, len(commandDescs))
for i, c := range commandDescs {
names[i] = c.name
}
return strings.Join(names, ", ")
}
+3
View File
@@ -0,0 +1,3 @@
module gitea.libretechconsulting.com/rmcguire/wingbits
go 1.26.4
+194
View File
@@ -0,0 +1,194 @@
// Package client is a client for a Wingbits station (such as the MGW310). It
// exposes the readsb JSON files (aircraft, receiver, stats, outline) and the
// Wingbits-specific endpoints (network diagnostics, Prometheus metrics and
// Tailscale status) as typed Go values, with both one-shot queries and
// channel-based streaming.
//
// Decoded values come from the type-only packages pkg/types/readsb and
// pkg/types/wingbits; this package owns the transport and request logic.
//
// A station serves two HTTP roots:
//
// - The Wingbits root (default :8088) serves the Wingbits endpoints directly
// and the readsb files under readsb/.
// - The tar1090 root (default :8504) serves the readsb files under data/,
// plus data/outline.json. When configured it overrides the Wingbits root
// for all readsb files.
package client
import (
"crypto/tls"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
// Default ports exposed by an MGW310 station.
const (
DefaultWingbitsPort = 8088
DefaultTar1090Port = 8504
// defaultDataPath is the tar1090 path under which readsb files are served.
defaultDataPath = "data"
// wingbitsReadsbPath is the path under which the Wingbits root serves readsb files.
wingbitsReadsbPath = "readsb"
)
// Endpoint describes one HTTP root on a station.
type Endpoint struct {
// Scheme is "http" (default) or "https".
Scheme string
// Host is the hostname or IP of the station.
Host string
// Port is the TCP port; 0 selects the role's default.
Port int
// DataPath is the URL path prefix under which readsb files are served. For
// the tar1090 root this defaults to "data"; for the Wingbits root the
// readsb files live under "readsb".
DataPath string
// TLS configures the transport when Scheme is "https". It is only applied
// when the client builds its own http.Client (i.e. no WithHTTPClient).
TLS *tls.Config
}
func (e Endpoint) scheme() string {
if e.Scheme != "" {
return e.Scheme
}
return "http"
}
func (e Endpoint) hostPort(defPort int) string {
port := e.Port
if port == 0 {
port = defPort
}
return fmt.Sprintf("%s:%d", e.Host, port)
}
// url builds an absolute URL for path elements joined under the endpoint,
// optionally prefixed by the endpoint's data path.
func (e Endpoint) url(defPort int, withData bool, elem ...string) string {
parts := elem
if withData && e.DataPath != "" {
parts = append([]string{e.DataPath}, elem...)
}
u := url.URL{
Scheme: e.scheme(),
Host: e.hostPort(defPort),
Path: "/" + strings.Join(parts, "/"),
}
return u.String()
}
// Client talks to a Wingbits station. Construct it with New or NewMGW310Client.
// It is safe for concurrent use.
type Client struct {
wingbits Endpoint
tar1090 *Endpoint
http *http.Client
streamBuf int
userAgent string
}
// Option customizes a Client.
type Option func(*Client)
// WithWingbitsEndpoint sets (and overrides) the Wingbits HTTP root.
func WithWingbitsEndpoint(e Endpoint) Option {
return func(c *Client) { c.wingbits = e }
}
// WithTar1090Endpoint enables a tar1090 HTTP root. When set it overrides the
// Wingbits root for all readsb files (aircraft, receiver, stats) and is the
// only source for outline.json.
func WithTar1090Endpoint(e Endpoint) Option {
return func(c *Client) {
if e.DataPath == "" {
e.DataPath = defaultDataPath
}
c.tar1090 = &e
}
}
// WithHTTPClient supplies a custom http.Client. When provided, per-endpoint TLS
// config is ignored (configure it on the supplied client's transport instead).
func WithHTTPClient(h *http.Client) Option {
return func(c *Client) { c.http = h }
}
// WithStreamBufferSize sets the buffer size of channels returned by the Stream*
// methods. The default is 1.
func WithStreamBufferSize(n int) Option {
return func(c *Client) {
if n >= 0 {
c.streamBuf = n
}
}
}
// WithUserAgent overrides the User-Agent header sent with requests.
func WithUserAgent(ua string) Option {
return func(c *Client) { c.userAgent = ua }
}
// New constructs a Client from options. A Wingbits endpoint with a non-empty
// Host must be supplied via WithWingbitsEndpoint.
func New(opts ...Option) (*Client, error) {
c := &Client{
streamBuf: 1,
userAgent: "wingbits-go/1.0",
}
for _, opt := range opts {
opt(c)
}
if c.wingbits.DataPath == "" {
c.wingbits.DataPath = wingbitsReadsbPath
}
if c.wingbits.Host == "" {
return nil, fmt.Errorf("wingbits: a wingbits endpoint host is required")
}
if c.http == nil {
c.http = buildHTTPClient(c.wingbits, c.tar1090)
}
return c, nil
}
// NewMGW310Client is a convenience constructor for a standard MGW310 reachable
// at host. It wires the Wingbits root to :8088 and a tar1090 root to :8504, then
// applies any options (which may override either endpoint, TLS, the HTTP client,
// and so on).
func NewMGW310Client(host string, opts ...Option) (*Client, error) {
base := []Option{
WithWingbitsEndpoint(Endpoint{Host: host, Port: DefaultWingbitsPort, DataPath: wingbitsReadsbPath}),
WithTar1090Endpoint(Endpoint{Host: host, Port: DefaultTar1090Port, DataPath: defaultDataPath}),
}
return New(append(base, opts...)...)
}
// readsbEndpoint returns the endpoint and default port serving readsb files,
// preferring a configured tar1090 root.
func (c *Client) readsbEndpoint() (Endpoint, int) {
if c.tar1090 != nil {
return *c.tar1090, DefaultTar1090Port
}
return c.wingbits, DefaultWingbitsPort
}
// buildHTTPClient creates a default client, attaching TLS config from whichever
// endpoint supplies one (the tar1090 root takes precedence when both do).
func buildHTTPClient(wb Endpoint, tar *Endpoint) *http.Client {
var tlsCfg *tls.Config
if wb.TLS != nil {
tlsCfg = wb.TLS
}
if tar != nil && tar.TLS != nil {
tlsCfg = tar.TLS
}
tr := &http.Transport{
TLSClientConfig: tlsCfg,
MaxIdleConnsPerHost: 2,
}
return &http.Client{Timeout: 15 * time.Second, Transport: tr}
}
+164
View File
@@ -0,0 +1,164 @@
package client
import (
"context"
"net/http"
"net/http/httptest"
"os"
"strconv"
"strings"
"testing"
"time"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/readsb"
)
// fixtures map a request path to a captured station response. The readsb files
// and the Wingbits files are reused from their respective type packages' testdata
// so there is a single source of truth for sample payloads.
var fixtures = map[string]string{
"/readsb/aircraft.json": "../types/readsb/testdata/aircraft.json",
"/data/aircraft.json": "../types/readsb/testdata/aircraft.json",
"/data/receiver.json": "../types/readsb/testdata/receiver.json",
"/data/stats.json": "../types/readsb/testdata/stats.json",
"/data/outline.json": "../types/readsb/testdata/outline.json",
"/network/diagnostics": "../types/wingbits/testdata/diagnostics.json",
"/tailscale/status": "../types/wingbits/testdata/status.json",
"/metrics": "../types/wingbits/testdata/metrics.txt",
}
// newTestClient stands up an httptest server that serves the captured station
// fixtures from both the Wingbits (readsb/) and tar1090 (data/) roots.
func newTestClient(t *testing.T) *Client {
t.Helper()
mux := http.NewServeMux()
for path, file := range fixtures {
mux.HandleFunc(path, serveFile(file))
}
srv := httptest.NewServer(mux)
t.Cleanup(srv.Close)
host, port := splitHostPort(t, strings.TrimPrefix(srv.URL, "http://"))
c, err := New(
WithWingbitsEndpoint(Endpoint{Host: host, Port: port, DataPath: wingbitsReadsbPath}),
WithTar1090Endpoint(Endpoint{Host: host, Port: port, DataPath: defaultDataPath}),
)
if err != nil {
t.Fatal(err)
}
return c
}
func serveFile(file string) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
b, err := os.ReadFile(file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}
}
func splitHostPort(t *testing.T, hp string) (string, int) {
t.Helper()
h, p, ok := strings.Cut(hp, ":")
if !ok {
t.Fatalf("bad host:port %q", hp)
}
port, err := strconv.Atoi(p)
if err != nil {
t.Fatalf("bad port %q: %v", p, err)
}
return h, port
}
func TestClientReadsbEndpoints(t *testing.T) {
c := newTestClient(t)
ctx := context.Background()
all, err := c.Aircraft(ctx)
if err != nil || len(all.Aircraft) == 0 {
t.Fatalf("aircraft: %v (%d)", err, len(all.Aircraft))
}
// Filters supplied to the query are applied before returning.
pos, err := c.Aircraft(ctx, readsb.WithPosition())
if err != nil {
t.Fatal(err)
}
if len(pos.Aircraft) == 0 || len(pos.Aircraft) > len(all.Aircraft) {
t.Errorf("filtered %d of %d", len(pos.Aircraft), len(all.Aircraft))
}
rc, err := c.Receiver(ctx)
if err != nil || !rc.HasLocation() {
t.Fatalf("receiver: %v", err)
}
if _, err := c.Stats(ctx); err != nil {
t.Fatalf("stats: %v", err)
}
o, err := c.Outline(ctx)
if err != nil || len(o.ActualRange.Last24h.Points) == 0 {
t.Fatalf("outline: %v", err)
}
}
func TestOutlineRequiresTar1090(t *testing.T) {
c, err := New(WithWingbitsEndpoint(Endpoint{Host: "127.0.0.1"}))
if err != nil {
t.Fatal(err)
}
if _, err := c.Outline(context.Background()); err == nil {
t.Error("expected error without tar1090 endpoint")
}
}
func TestClientWingbitsEndpoints(t *testing.T) {
c := newTestClient(t)
ctx := context.Background()
d, err := c.Diagnostics(ctx)
if err != nil || d.Hostname == "" {
t.Fatalf("diagnostics: %v", err)
}
if !d.AllReachable() {
t.Error("expected fixture probes all reachable")
}
s, err := c.Status(ctx)
if err != nil || s.Hostname == "" {
t.Fatalf("status: %v", err)
}
m, err := c.Metrics(ctx)
if err != nil {
t.Fatalf("metrics: %v", err)
}
wb := m.Wingbits()
if wb.Version == "" {
t.Error("expected wingbits_version label")
}
if !wb.NATS.Connected {
t.Error("expected NATS connected in fixture")
}
if wb.Beast.Received == 0 {
t.Error("expected beast received counter")
}
}
func TestStreamAircraft(t *testing.T) {
c := newTestClient(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ch := c.StreamAircraft(ctx, time.Hour)
select {
case u := <-ch:
if u.Err != nil || u.Value == nil || len(u.Value.Aircraft) == 0 {
t.Fatalf("first update bad: %v", u.Err)
}
case <-time.After(5 * time.Second):
t.Fatal("no first update")
}
cancel()
for range ch { // channel must drain and close after cancellation
}
}
+98
View File
@@ -0,0 +1,98 @@
package client
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/readsb"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/wingbits"
)
// Aircraft fetches and decodes aircraft.json. When one or more filters are
// supplied, only matching aircraft are retained in the returned report.
func (c *Client) Aircraft(ctx context.Context, filters ...readsb.AircraftFilter) (*readsb.AircraftReport, error) {
ep, port := c.readsbEndpoint()
report, err := getJSON[readsb.AircraftReport](ctx, c, ep.url(port, true, "aircraft.json"))
if err != nil {
return nil, err
}
report.Aircraft = report.Filter(filters...)
return report, nil
}
// Receiver fetches and decodes receiver.json.
func (c *Client) Receiver(ctx context.Context) (*readsb.Receiver, error) {
ep, port := c.readsbEndpoint()
return getJSON[readsb.Receiver](ctx, c, ep.url(port, true, "receiver.json"))
}
// Stats fetches and decodes stats.json.
func (c *Client) Stats(ctx context.Context) (*readsb.Stats, error) {
ep, port := c.readsbEndpoint()
return getJSON[readsb.Stats](ctx, c, ep.url(port, true, "stats.json"))
}
// Outline fetches and decodes outline.json (the receiver range polygon). It is
// only served by the tar1090 root, so a tar1090 endpoint must be configured.
func (c *Client) Outline(ctx context.Context) (*readsb.Outline, error) {
if c.tar1090 == nil {
return nil, fmt.Errorf("wingbits: outline.json requires a tar1090 endpoint")
}
return getJSON[readsb.Outline](ctx, c, c.tar1090.url(DefaultTar1090Port, true, "outline.json"))
}
// Diagnostics fetches and decodes /network/diagnostics from the Wingbits root.
func (c *Client) Diagnostics(ctx context.Context) (*wingbits.Diagnostics, error) {
return getJSON[wingbits.Diagnostics](ctx, c, c.wingbits.url(DefaultWingbitsPort, false, "network", "diagnostics"))
}
// Status fetches and decodes the Tailscale status from the Wingbits root.
func (c *Client) Status(ctx context.Context) (*wingbits.TailscaleStatus, error) {
return getJSON[wingbits.TailscaleStatus](ctx, c, c.wingbits.url(DefaultWingbitsPort, false, "tailscale", "status"))
}
// Metrics fetches /metrics from the Wingbits root and parses the Prometheus
// text exposition format into structured metric families.
func (c *Client) Metrics(ctx context.Context) (*wingbits.Metrics, error) {
body, err := c.get(ctx, c.wingbits.url(DefaultWingbitsPort, false, "metrics"))
if err != nil {
return nil, err
}
defer body.Close()
return wingbits.ParseMetrics(body)
}
// getJSON fetches a URL and decodes its JSON body into a freshly allocated T.
func getJSON[T any](ctx context.Context, c *Client, url string) (*T, error) {
body, err := c.get(ctx, url)
if err != nil {
return nil, err
}
defer body.Close()
out := new(T)
if err := json.NewDecoder(body).Decode(out); err != nil {
return nil, fmt.Errorf("wingbits: decoding %s: %w", url, err)
}
return out, nil
}
// get performs a GET and returns the response body, which the caller must close.
func (c *Client) get(ctx context.Context, url string) (io.ReadCloser, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("wingbits: building request: %w", err)
}
req.Header.Set("User-Agent", c.userAgent)
resp, err := c.http.Do(req)
if err != nil {
return nil, fmt.Errorf("wingbits: GET %s: %w", url, err)
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("wingbits: GET %s: unexpected status %s", url, resp.Status)
}
return resp.Body, nil
}
+85
View File
@@ -0,0 +1,85 @@
package client
import (
"context"
"time"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/readsb"
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/wingbits"
)
// Update carries one streamed sample of T, or the error from the fetch that
// produced it. Exactly one of Value and Err is meaningful per send.
type Update[T any] struct {
Value *T
Err error
}
// StreamAircraft polls aircraft.json every interval and sends each decoded,
// filtered report on the returned channel until ctx is cancelled. The first
// sample is sent immediately. The channel is closed when ctx ends.
func (c *Client) StreamAircraft(ctx context.Context, interval time.Duration, filters ...readsb.AircraftFilter) <-chan Update[readsb.AircraftReport] {
return stream(ctx, c.streamBuf, interval, func(ctx context.Context) (*readsb.AircraftReport, error) {
return c.Aircraft(ctx, filters...)
})
}
// StreamStats polls stats.json every interval.
func (c *Client) StreamStats(ctx context.Context, interval time.Duration) <-chan Update[readsb.Stats] {
return stream(ctx, c.streamBuf, interval, c.Stats)
}
// StreamReceiver polls receiver.json every interval.
func (c *Client) StreamReceiver(ctx context.Context, interval time.Duration) <-chan Update[readsb.Receiver] {
return stream(ctx, c.streamBuf, interval, c.Receiver)
}
// StreamOutline polls outline.json every interval.
func (c *Client) StreamOutline(ctx context.Context, interval time.Duration) <-chan Update[readsb.Outline] {
return stream(ctx, c.streamBuf, interval, c.Outline)
}
// StreamDiagnostics polls /network/diagnostics every interval.
func (c *Client) StreamDiagnostics(ctx context.Context, interval time.Duration) <-chan Update[wingbits.Diagnostics] {
return stream(ctx, c.streamBuf, interval, c.Diagnostics)
}
// StreamMetrics polls /metrics every interval.
func (c *Client) StreamMetrics(ctx context.Context, interval time.Duration) <-chan Update[wingbits.Metrics] {
return stream(ctx, c.streamBuf, interval, c.Metrics)
}
// StreamStatus polls the Tailscale status every interval.
func (c *Client) StreamStatus(ctx context.Context, interval time.Duration) <-chan Update[wingbits.TailscaleStatus] {
return stream(ctx, c.streamBuf, interval, c.Status)
}
// stream drives a generic poll loop: it calls fetch immediately, then once per
// interval tick, forwarding each result as an Update on a buffered channel.
func stream[T any](ctx context.Context, buf int, interval time.Duration, fetch func(context.Context) (*T, error)) <-chan Update[T] {
ch := make(chan Update[T], buf)
go func() {
defer close(ch)
tick := time.NewTicker(interval)
defer tick.Stop()
for {
send(ctx, ch, fetch)
select {
case <-ctx.Done():
return
case <-tick.C:
}
}
}()
return ch
}
// send runs one fetch and delivers the result, respecting cancellation so a
// full channel cannot wedge the loop past ctx's lifetime.
func send[T any](ctx context.Context, ch chan<- Update[T], fetch func(context.Context) (*T, error)) {
v, err := fetch(ctx)
select {
case ch <- Update[T]{Value: v, Err: err}:
case <-ctx.Done():
}
}
+192
View File
@@ -0,0 +1,192 @@
// Package readsb provides Go types and helpers for decoding the JSON files
// produced by readsb / tar1090 (aircraft.json, receiver.json, stats.json and
// outline.json).
//
// The field documentation follows wiedehopf's readsb README-json.md:
// https://github.com/wiedehopf/readsb/blob/dev/README-json.md
package readsb
import (
"bytes"
"encoding/json"
"strconv"
"time"
)
// AircraftReport is the decoded form of aircraft.json: a snapshot of every
// aircraft readsb is currently tracking.
type AircraftReport struct {
// Now is the time this file was generated.
Now UnixTime `json:"now"`
// Messages is the total number of Mode S messages processed since startup.
Messages int64 `json:"messages"`
// Aircraft holds one entry per tracked aircraft.
Aircraft []Aircraft `json:"aircraft"`
}
// Aircraft is a single tracked target. Most fields are optional: readsb only
// emits a key once it has decoded a value for it, so callers should treat the
// zero value as "unknown" and lean on the typed accessors where provided.
type Aircraft struct {
// Hex is the 24-bit ICAO address as a 6-hex-digit string. A leading '~'
// indicates a non-ICAO address (TIS-B).
Hex string `json:"hex"`
// Type is the message source/type, e.g. "adsb_icao", "mode_s", "mlat".
Type string `json:"type"`
// Flight is the callsign. readsb space-pads it; use Callsign for a trimmed value.
Flight string `json:"flight,omitempty"`
// Registration (r) and aircraft type (t) come from an optional database.
Registration string `json:"r,omitempty"`
AircraftType string `json:"t,omitempty"`
// AltBaro is the barometric altitude in feet, or on-ground (see AltBaro).
AltBaro AltBaro `json:"alt_baro,omitzero"`
// AltGeom is the geometric (GNSS/INS) altitude in feet referenced to WGS84.
AltGeom int `json:"alt_geom,omitempty"`
// GS is ground speed in knots.
GS float64 `json:"gs,omitempty"`
// IAS/TAS are indicated/true airspeed in knots.
IAS int `json:"ias,omitempty"`
TAS int `json:"tas,omitempty"`
// Mach is the Mach number.
Mach float64 `json:"mach,omitempty"`
// Track is the true track over ground in degrees (0-359).
Track float64 `json:"track,omitempty"`
// CalcTrack is a track estimated from position when not transmitted.
CalcTrack float64 `json:"calc_track,omitempty"`
// BaroRate / GeomRate are the climb rate in feet/minute (baro vs geometric).
BaroRate int `json:"baro_rate,omitempty"`
GeomRate int `json:"geom_rate,omitempty"`
// Squawk is the Mode A code as 4 octal digits.
Squawk string `json:"squawk,omitempty"`
// Emergency is the emergency/priority status; "none" when not declared.
Emergency string `json:"emergency,omitempty"`
// Category is the emitter category, e.g. "A0".."A7", "B0".. (see README-json).
Category string `json:"category,omitempty"`
// Lat/Lon are the last known position in decimal degrees.
Lat float64 `json:"lat,omitempty"`
Lon float64 `json:"lon,omitempty"`
// LastPosition carries a stale position when the current one is too old to
// be reported inline (i.e. when SeenPos exceeds the staleness threshold).
LastPosition *Position `json:"lastPosition,omitempty"`
// NIC / RC describe positional integrity: navigation integrity category and
// the radius of containment in metres.
NIC int `json:"nic,omitempty"`
RC int `json:"rc,omitempty"`
// SeenPos is seconds since the last position update.
SeenPos float64 `json:"seen_pos,omitempty"`
// RDst / RDir are range (nm) and bearing (deg) from the receiver, when known.
RDst float64 `json:"r_dst,omitempty"`
RDir float64 `json:"r_dir,omitempty"`
// Navigation intent reported by the FMS/autopilot.
NavQNH float64 `json:"nav_qnh,omitempty"`
NavAltitudeMCP int `json:"nav_altitude_mcp,omitempty"`
NavAltitudeFMS int `json:"nav_altitude_fms,omitempty"`
NavHeading float64 `json:"nav_heading,omitempty"`
NavModes []string `json:"nav_modes,omitempty"`
// ADS-B version and quality/accuracy indicators.
Version int `json:"version,omitempty"`
NICBaro int `json:"nic_baro,omitempty"`
NACP int `json:"nac_p,omitempty"`
NACV int `json:"nac_v,omitempty"`
SIL int `json:"sil,omitempty"`
SILType string `json:"sil_type,omitempty"`
GVA int `json:"gva,omitempty"`
SDA int `json:"sda,omitempty"`
// Alert and special position identification flags.
Alert int `json:"alert,omitempty"`
SPI int `json:"spi,omitempty"`
// MLAT / TISB list the field names whose values were derived by
// multilateration or received via TIS-B rather than direct ADS-B.
MLAT []string `json:"mlat"`
TISB []string `json:"tisb"`
// Messages is the total number of messages received for this aircraft.
Messages int `json:"messages,omitempty"`
// Seen is seconds since any message was last received from this aircraft.
Seen float64 `json:"seen,omitempty"`
// RSSI is the average signal power of received messages, in dBFS.
RSSI float64 `json:"rssi,omitempty"`
}
// Position is a timestamped lat/lon with integrity metadata, used by the
// lastPosition field.
type Position struct {
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
NIC int `json:"nic,omitempty"`
RC int `json:"rc,omitempty"`
SeenPos float64 `json:"seen_pos,omitempty"`
}
// Callsign returns Flight with readsb's trailing padding removed.
func (a Aircraft) Callsign() string { return trimPad(a.Flight) }
// HasPosition reports whether the aircraft has a usable live lat/lon.
func (a Aircraft) HasPosition() bool { return a.Lat != 0 || a.Lon != 0 }
// InEmergency reports whether a non-routine emergency/priority code is set.
func (a Aircraft) InEmergency() bool {
return a.Emergency != "" && a.Emergency != "none"
}
// SeenFor returns the time since the last message was received.
func (a Aircraft) SeenFor() time.Duration {
return time.Duration(a.Seen * float64(time.Second))
}
func trimPad(s string) string {
return string(bytes.TrimSpace([]byte(s)))
}
// AltBaro is a barometric altitude in feet. readsb emits the JSON string
// "ground" (rather than a number) when the target reports being on the ground;
// that case is decoded as OnGround=true with Feet=0.
type AltBaro struct {
Feet int
OnGround bool
}
// UnmarshalJSON accepts either a number of feet or the literal string "ground".
func (a *AltBaro) UnmarshalJSON(b []byte) error {
if bytes.Equal(b, []byte(`"ground"`)) {
a.OnGround, a.Feet = true, 0
return nil
}
n, err := strconv.Atoi(string(bytes.Trim(b, `"`)))
a.Feet, a.OnGround = n, false
return err
}
// MarshalJSON renders the on-ground case back to "ground", else the feet value.
func (a AltBaro) MarshalJSON() ([]byte, error) {
if a.OnGround {
return []byte(`"ground"`), nil
}
return []byte(strconv.Itoa(a.Feet)), nil
}
// UnixTime decodes readsb's fractional-second Unix timestamps into time.Time.
type UnixTime struct{ time.Time }
// UnmarshalJSON parses a JSON number of (fractional) seconds since the epoch.
func (u *UnixTime) UnmarshalJSON(b []byte) error {
var f float64
if err := json.Unmarshal(b, &f); err != nil {
return err
}
sec, frac := splitFloat(f)
u.Time = time.Unix(sec, frac)
return nil
}
func splitFloat(f float64) (sec int64, nsec int64) {
sec = int64(f)
return sec, int64((f - float64(sec)) * 1e9)
}
+141
View File
@@ -0,0 +1,141 @@
package readsb
import (
"strings"
"time"
)
// AircraftFilter reports whether an aircraft should be kept. Filters compose:
// an aircraft is kept only if every filter passes (logical AND).
type AircraftFilter func(*Aircraft) bool
// Filter returns the aircraft that satisfy all of the supplied filters. With no
// filters it returns every aircraft. The receiver report is never mutated.
func (r *AircraftReport) Filter(filters ...AircraftFilter) []Aircraft {
if len(filters) == 0 {
return r.Aircraft
}
out := make([]Aircraft, 0, len(r.Aircraft))
for i := range r.Aircraft {
if keep(&r.Aircraft[i], filters) {
out = append(out, r.Aircraft[i])
}
}
return out
}
func keep(a *Aircraft, filters []AircraftFilter) bool {
for _, f := range filters {
if !f(a) {
return false
}
}
return true
}
// WithPosition keeps only aircraft that currently report a lat/lon.
func WithPosition() AircraftFilter {
return func(a *Aircraft) bool { return a.HasPosition() }
}
// WithHex keeps aircraft whose ICAO address matches any of the given values
// (case-insensitive, leading '~' ignored).
func WithHex(hexes ...string) AircraftFilter {
set := lowerSet(strings.TrimPrefix, hexes, "~")
return func(a *Aircraft) bool { return set[strings.ToLower(strings.TrimPrefix(a.Hex, "~"))] }
}
// WithCallsign keeps aircraft whose (trimmed) callsign matches any value, case-insensitively.
func WithCallsign(calls ...string) AircraftFilter {
set := lowerSet(nil, calls, "")
return func(a *Aircraft) bool { return set[strings.ToLower(a.Callsign())] }
}
// WithSquawk keeps aircraft transmitting any of the given Mode A squawk codes.
func WithSquawk(codes ...string) AircraftFilter {
set := lowerSet(nil, codes, "")
return func(a *Aircraft) bool { return set[strings.ToLower(a.Squawk)] }
}
// WithCategory keeps aircraft of any of the given emitter categories (e.g. "A3").
func WithCategory(cats ...string) AircraftFilter {
set := lowerSet(nil, cats, "")
return func(a *Aircraft) bool { return set[strings.ToLower(a.Category)] }
}
// WithType keeps aircraft of any of the given source types (e.g. "adsb_icao", "mlat").
func WithType(types ...string) AircraftFilter {
set := lowerSet(nil, types, "")
return func(a *Aircraft) bool { return set[strings.ToLower(a.Type)] }
}
// InEmergency keeps only aircraft squawking a non-routine emergency/priority code.
func InEmergency() AircraftFilter {
return func(a *Aircraft) bool { return a.InEmergency() }
}
// IsMLAT keeps only aircraft whose position was derived by multilateration.
func IsMLAT() AircraftFilter {
return func(a *Aircraft) bool { return a.Type == "mlat" || len(a.MLAT) > 0 }
}
// MinAltitude keeps airborne aircraft at or above the given barometric altitude (feet).
func MinAltitude(feet int) AircraftFilter {
return func(a *Aircraft) bool { return !a.AltBaro.OnGround && a.AltBaro.Feet >= feet }
}
// MaxAltitude keeps aircraft at or below the given barometric altitude (feet);
// on-ground aircraft always pass.
func MaxAltitude(feet int) AircraftFilter {
return func(a *Aircraft) bool { return a.AltBaro.OnGround || a.AltBaro.Feet <= feet }
}
// OnGround keeps only aircraft reporting an on-ground barometric altitude.
func OnGround() AircraftFilter {
return func(a *Aircraft) bool { return a.AltBaro.OnGround }
}
// WithinNM keeps aircraft within the given range (nautical miles) of the receiver.
func WithinNM(nm float64) AircraftFilter {
return func(a *Aircraft) bool { return a.RDst > 0 && a.RDst <= nm }
}
// SeenWithin keeps aircraft heard from within the given duration.
func SeenWithin(d time.Duration) AircraftFilter {
return func(a *Aircraft) bool { return a.SeenFor() <= d }
}
// MinRSSI keeps aircraft whose average signal strength is at or above dbfs (e.g. -24).
func MinRSSI(dbfs float64) AircraftFilter {
return func(a *Aircraft) bool { return a.RSSI >= dbfs }
}
// Not inverts a filter.
func Not(f AircraftFilter) AircraftFilter {
return func(a *Aircraft) bool { return !f(a) }
}
// Any keeps aircraft matching at least one of the supplied filters (logical OR).
func Any(filters ...AircraftFilter) AircraftFilter {
return func(a *Aircraft) bool {
for _, f := range filters {
if f(a) {
return true
}
}
return false
}
}
// lowerSet builds a lowercased lookup set, optionally pre-processing each value
// with trim(value, cut) when trim is non-nil.
func lowerSet(trim func(string, string) string, values []string, cut string) map[string]bool {
set := make(map[string]bool, len(values))
for _, v := range values {
if trim != nil {
v = trim(v, cut)
}
set[strings.ToLower(v)] = true
}
return set
}
+45
View File
@@ -0,0 +1,45 @@
package readsb
import "encoding/json"
// Outline is the decoded form of data/outline.json (served by tar1090). It
// describes the receiver's observed reception range as a polygon of the
// farthest positions seen per bearing.
type Outline struct {
ActualRange ActualRange `json:"actualRange"`
}
// ActualRange groups range polygons by time window. tar1090 currently emits a
// single "last24h" window.
type ActualRange struct {
Last24h RangeOutline `json:"last24h"`
}
// RangeOutline holds the ordered set of range points forming the outline polygon.
type RangeOutline struct {
Points []RangePoint `json:"points"`
}
// RangePoint is one vertex of the range polygon: the farthest aircraft position
// observed along a given bearing, with the altitude at which it was seen. It
// decodes from readsb's compact [lat, lon, altFeet] array form.
type RangePoint struct {
Lat float64
Lon float64
Alt int
}
// UnmarshalJSON decodes the [lat, lon, alt] tuple form used by outline.json.
func (p *RangePoint) UnmarshalJSON(b []byte) error {
var t [3]float64
if err := json.Unmarshal(b, &t); err != nil {
return err
}
p.Lat, p.Lon, p.Alt = t[0], t[1], int(t[2])
return nil
}
// MarshalJSON re-encodes the point as the compact [lat, lon, alt] tuple.
func (p RangePoint) MarshalJSON() ([]byte, error) {
return json.Marshal([3]float64{p.Lat, p.Lon, float64(p.Alt)})
}
+118
View File
@@ -0,0 +1,118 @@
package readsb
import (
"encoding/json"
"os"
"testing"
"time"
)
func decodeFile[T any](t *testing.T, name string) *T {
t.Helper()
b, err := os.ReadFile("testdata/" + name)
if err != nil {
t.Fatalf("read %s: %v", name, err)
}
out := new(T)
if err := json.Unmarshal(b, out); err != nil {
t.Fatalf("decode %s: %v", name, err)
}
return out
}
func TestDecodeAircraft(t *testing.T) {
r := decodeFile[AircraftReport](t, "aircraft.json")
if len(r.Aircraft) == 0 {
t.Fatal("no aircraft decoded")
}
if r.Now.IsZero() {
t.Error("now timestamp not decoded")
}
var withPos, withLast int
for _, a := range r.Aircraft {
if a.HasPosition() {
withPos++
}
if a.LastPosition != nil {
withLast++
}
if a.Hex == "" {
t.Error("aircraft with empty hex")
}
}
if withPos == 0 {
t.Error("expected some aircraft with positions")
}
if withLast == 0 {
t.Error("expected the lastPosition quirk to appear in sample data")
}
}
func TestAircraftFilters(t *testing.T) {
r := decodeFile[AircraftReport](t, "aircraft.json")
all := len(r.Aircraft)
pos := r.Filter(WithPosition())
if len(pos) == 0 || len(pos) > all {
t.Errorf("WithPosition returned %d of %d", len(pos), all)
}
high := r.Filter(MinAltitude(30000))
for _, a := range high {
if a.AltBaro.OnGround || a.AltBaro.Feet < 30000 {
t.Errorf("MinAltitude kept %s at %+v", a.Hex, a.AltBaro)
}
}
near := r.Filter(WithinNM(50))
for _, a := range near {
if a.RDst <= 0 || a.RDst > 50 {
t.Errorf("WithinNM kept %s at %.1f nm", a.Hex, a.RDst)
}
}
// Composition is AND: position AND high altitude is a subset of each.
both := r.Filter(WithPosition(), MinAltitude(30000))
if len(both) > len(pos) || len(both) > len(high) {
t.Errorf("composed filter not a subset: %d vs pos %d high %d", len(both), len(pos), len(high))
}
}
func TestAltBaroGround(t *testing.T) {
var a AltBaro
if err := json.Unmarshal([]byte(`"ground"`), &a); err != nil {
t.Fatal(err)
}
if !a.OnGround {
t.Error("expected OnGround")
}
if err := json.Unmarshal([]byte(`38000`), &a); err != nil {
t.Fatal(err)
}
if a.OnGround || a.Feet != 38000 {
t.Errorf("got %+v", a)
}
b, _ := json.Marshal(AltBaro{OnGround: true})
if string(b) != `"ground"` {
t.Errorf("marshal got %s", b)
}
}
func TestDecodeReceiverStatsOutline(t *testing.T) {
rc := decodeFile[Receiver](t, "receiver.json")
if !rc.HasLocation() || rc.Version == "" {
t.Errorf("receiver not decoded: %+v", rc)
}
s := decodeFile[Stats](t, "stats.json")
if s.Total.Messages == 0 || s.Last1Min.End.Before(s.Last1Min.Start.Time) {
t.Errorf("stats not decoded: total messages %d", s.Total.Messages)
}
if s.Now.Sub(s.Last1Min.End.Time) > time.Hour {
t.Error("now and window end implausibly far apart")
}
o := decodeFile[Outline](t, "outline.json")
if len(o.ActualRange.Last24h.Points) == 0 {
t.Fatal("no outline points")
}
p := o.ActualRange.Last24h.Points[0]
if p.Lat == 0 || p.Lon == 0 {
t.Errorf("outline point not decoded: %+v", p)
}
}
+26
View File
@@ -0,0 +1,26 @@
package readsb
// Receiver is the decoded form of receiver.json. It describes the running
// readsb instance and is mostly consumed by the tar1090 web UI, but the lat/lon
// and version are useful for orienting clients.
type Receiver struct {
// Version is the readsb build string.
Version string `json:"version"`
// Refresh is the suggested aircraft.json poll interval in milliseconds.
Refresh int `json:"refresh"`
// History is the number of history_N.json files available.
History int `json:"history"`
// Lat/Lon is the configured receiver location, when set.
Lat float64 `json:"lat,omitempty"`
Lon float64 `json:"lon,omitempty"`
// JaeroTimeout is the timeout (minutes) for JAERO/ACARS positions.
JaeroTimeout float64 `json:"jaeroTimeout,omitempty"`
// Capability flags advertised to the web UI.
ReadSB bool `json:"readsb,omitempty"`
BinCraft bool `json:"binCraft,omitempty"`
Zstd bool `json:"zstd,omitempty"`
OutlineJSON bool `json:"outlineJson,omitempty"`
}
// HasLocation reports whether the receiver has a configured position.
func (r Receiver) HasLocation() bool { return r.Lat != 0 || r.Lon != 0 }
+136
View File
@@ -0,0 +1,136 @@
package readsb
// Stats is the decoded form of stats.json. readsb reports counters over several
// rolling windows plus a cumulative total since startup.
type Stats struct {
// Now is the time the file was generated.
Now UnixTime `json:"now"`
// GainDB is the current tuner gain in dB.
GainDB float64 `json:"gain_db"`
// EstimatedPPM is the estimated tuner frequency error in parts per million.
EstimatedPPM float64 `json:"estimated_ppm"`
// AircraftWithPos / AircraftWithoutPos count currently tracked aircraft.
AircraftWithPos int `json:"aircraft_with_pos"`
AircraftWithoutPos int `json:"aircraft_without_pos"`
// AircraftByType counts currently tracked aircraft by source type.
AircraftByType CountsByType `json:"aircraft_count_by_type"`
// Rolling windows and the cumulative total.
Last1Min StatPeriod `json:"last1min"`
Last5Min StatPeriod `json:"last5min"`
Last15Min StatPeriod `json:"last15min"`
Total StatPeriod `json:"total"`
}
// CountsByType breaks a count down by readsb message source type.
type CountsByType struct {
ADSBICAO int `json:"adsb_icao"`
ADSBICAONT int `json:"adsb_icao_nt"`
ADSRICAO int `json:"adsr_icao"`
TISBICAO int `json:"tisb_icao"`
ADSC int `json:"adsc"`
MLAT int `json:"mlat"`
Other int `json:"other"`
ModeS int `json:"mode_s"`
ADSBOther int `json:"adsb_other"`
ADSROther int `json:"adsr_other"`
TISBTrackfle int `json:"tisb_trackfile"`
TISBOther int `json:"tisb_other"`
ModeAC int `json:"mode_ac"`
Unknown int `json:"unknown"`
}
// StatPeriod holds the counters for one reporting window. Window bounds are
// given by Start and End; the total window has Start at process startup.
type StatPeriod struct {
Start UnixTime `json:"start"`
End UnixTime `json:"end"`
Local LocalStats `json:"local"`
Remote RemoteStats `json:"remote"`
CPR CPRStats `json:"cpr"`
CPU CPUStats `json:"cpu"`
Tracks TrackStats `json:"tracks"`
// MessagesValid is the number of valid Mode S messages in the window.
MessagesValid int64 `json:"messages_valid"`
// Messages is the total messages processed in the window.
Messages int64 `json:"messages"`
// PositionCount is the number of positions decoded in the window.
PositionCount int64 `json:"position_count_total"`
PositionCountByType CountsByType `json:"position_count_by_type"`
// AltitudeSuppressed counts altitudes dropped as implausible.
AltitudeSuppressed int `json:"altitude_suppressed"`
// MaxDistance is the farthest position decoded in the window, in metres.
MaxDistance float64 `json:"max_distance"`
}
// LocalStats covers messages demodulated from the locally attached SDR.
type LocalStats struct {
SamplesProcessed int64 `json:"samples_processed"`
SamplesDropped int64 `json:"samples_dropped"`
SamplesLost int64 `json:"samples_lost"`
ModeAC int64 `json:"modeac"`
ModeS int64 `json:"modes"`
Bad int64 `json:"bad"`
UnknownICAO int64 `json:"unknown_icao"`
// Accepted[n] is the count of messages accepted after n bit corrections.
Accepted []int64 `json:"accepted"`
Signal float64 `json:"signal"`
Noise float64 `json:"noise"`
PeakSignal float64 `json:"peak_signal"`
StrongSignals int64 `json:"strong_signals"`
PrePhase1 []int64 `json:"pre_phase_1"`
BestPhase []int64 `json:"best_phase"`
}
// RemoteStats covers messages received from network feeds rather than the SDR.
type RemoteStats struct {
ModeAC int64 `json:"modeac"`
ModeS int64 `json:"modes"`
BaseStation int64 `json:"basestation"`
Bad int64 `json:"bad"`
UnknownICAO int64 `json:"unknown_icao"`
Accepted []int64 `json:"accepted"`
BytesIn int64 `json:"bytes_in"`
BytesOut int64 `json:"bytes_out"`
}
// CPRStats covers Compact Position Reporting decode outcomes.
type CPRStats struct {
Surface int64 `json:"surface"`
Airborne int64 `json:"airborne"`
GlobalOK int64 `json:"global_ok"`
GlobalBad int64 `json:"global_bad"`
GlobalRange int64 `json:"global_range"`
GlobalSpeed int64 `json:"global_speed"`
GlobalSkipped int64 `json:"global_skipped"`
LocalOK int64 `json:"local_ok"`
LocalAircraftRel int64 `json:"local_aircraft_relative"`
LocalReceiverRel int64 `json:"local_receiver_relative"`
LocalSkipped int64 `json:"local_skipped"`
LocalRange int64 `json:"local_range"`
LocalSpeed int64 `json:"local_speed"`
Filtered int64 `json:"filtered"`
}
// CPUStats reports milliseconds of CPU time spent in each readsb subsystem.
type CPUStats struct {
Demod int64 `json:"demod"`
Reader int64 `json:"reader"`
Background int64 `json:"background"`
AircraftJSON int64 `json:"aircraft_json"`
GlobeJSON int64 `json:"globe_json"`
BinCraft int64 `json:"binCraft"`
TraceJSON int64 `json:"trace_json"`
HeatmapAndState int64 `json:"heatmap_and_state"`
APIWorkers int64 `json:"api_workers"`
APIUpdate int64 `json:"api_update"`
RemoveStale int64 `json:"remove_stale"`
}
// TrackStats counts aircraft tracks created in the window.
type TrackStats struct {
All int64 `json:"all"`
SingleMessage int64 `json:"single_message"`
}
+247
View File
@@ -0,0 +1,247 @@
{ "now" : 1782178901.000,
"messages" : 89317021,
"aircraft" : [
{"hex":"a62665","type":"adsb_icao","calc_track":255,"category":"A2","lat":41.741436,"lon":-90.528302,"nic":8,"rc":186,"seen_pos":24.516,"r_dst":228.461,"r_dir":279.8,"version":0,"nac_p":8,"sil":2,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":23244,"seen":24.5,"rssi":-31.2},
{"hex":"a2f33a","type":"adsb_icao","flight":"N29B ","alt_baro":43000,"alt_geom":44425,"gs":509.2,"track":98.93,"geom_rate":0,"squawk":"6021","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":43008,"nav_heading":99.14,"lat":41.631958,"lon":-89.840324,"nic":8,"rc":186,"seen_pos":0.861,"r_dst":197.081,"r_dir":278.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":483,"seen":0.7,"rssi":-26.6},
{"hex":"a288e4","type":"adsb_icao","alt_baro":37000,"alt_geom":38425,"gs":501.9,"track":79.21,"baro_rate":0,"emergency":"none","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":80.16,"lat":41.360733,"lon":-89.411987,"nic":8,"rc":186,"seen_pos":0.574,"r_dst":176.739,"r_dir":274.3,"version":0,"nic_baro":1,"nac_p":8,"nac_v":1,"sil":2,"sil_type":"unknown","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":135,"seen":0.1,"rssi":-25.6},
{"hex":"a91535","type":"adsb_icao","flight":"UAL1790 ","alt_baro":35000,"alt_geom":36325,"gs":484.8,"track":81.10,"baro_rate":0,"squawk":"6554","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":35008,"nav_heading":82.97,"lat":41.459423,"lon":-89.077787,"nic":8,"rc":186,"seen_pos":1.551,"r_dst":162.018,"r_dir":276.6,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1039,"seen":1.4,"rssi":-23.1},
{"hex":"a9704d","type":"adsb_icao","flight":"DAL2166 ","alt_baro":37000,"alt_geom":38400,"gs":492.9,"track":79.72,"baro_rate":0,"squawk":"2006","emergency":"none","category":"A4","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":73.12,"lat":41.294302,"lon":-88.851617,"nic":8,"rc":186,"seen_pos":0.526,"r_dst":151.385,"r_dir":273.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2732,"seen":0.2,"rssi":-14.1},
{"hex":"a12798","type":"adsb_icao","alt_baro":36000,"alt_geom":37650,"gs":405.0,"track":270.57,"baro_rate":0,"category":"A3","lat":38.443350,"lon":-88.678711,"nic":8,"rc":186,"seen_pos":48.490,"r_dst":221.232,"r_dir":222.5,"version":0,"nac_p":8,"nac_v":2,"sil":2,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":6874,"seen":17.2,"rssi":-30.2},
{"hex":"a41c56","type":"adsb_icao","alt_baro":36975,"alt_geom":38200,"gs":499.0,"track":90.57,"baro_rate":0,"squawk":"3163","emergency":"none","lat":42.848728,"lon":-88.622785,"nic":8,"rc":186,"seen_pos":3.792,"r_dst":170.705,"r_dir":306.4,"version":0,"nac_p":8,"nac_v":2,"sil":2,"sil_type":"unknown","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":110,"seen":3.8,"rssi":-30.2},
{"hex":"a7b40e","type":"adsb_icao","flight":"DAL671 ","alt_baro":34975,"alt_geom":36200,"gs":494.0,"track":90.46,"baro_rate":0,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":35008,"nav_heading":0.00,"lat":42.568158,"lon":-88.612721,"nic":8,"rc":186,"seen_pos":9.051,"r_dst":161.434,"r_dir":301.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":250,"seen":1.7,"rssi":-30.1},
{"hex":"a8e29c","type":"adsb_icao","alt_baro":38000,"alt_geom":39650,"gs":450.2,"track":320.23,"baro_rate":0,"category":"A4","nav_qnh":1013.6,"nav_altitude_mcp":38016,"nav_heading":317.81,"lat":38.840487,"lon":-88.612183,"nic":8,"rc":186,"seen_pos":55.072,"r_dst":201.504,"r_dir":226.2,"version":0,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":229,"seen":15.4,"rssi":-31.1},
{"hex":"a4fd6f","type":"adsb_icao","flight":"UPS495 ","alt_baro":36975,"alt_geom":38325,"gs":471.5,"track":150.66,"baro_rate":0,"squawk":"1164","emergency":"none","category":"A4","nav_qnh":1012.8,"nav_altitude_mcp":36992,"nav_heading":160.31,"nav_modes":["vnav","tcas"],"lat":41.137514,"lon":-88.465472,"nic":8,"rc":186,"seen_pos":0.652,"r_dst":134.087,"r_dir":269.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":4044,"seen":0.7,"rssi":-23.1},
{"hex":"a8fc73","type":"adsb_icao","flight":"UAL1575 ","alt_baro":34000,"alt_geom":35325,"gs":418.2,"track":271.78,"baro_rate":0,"squawk":"3061","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":272.81,"lat":41.362152,"lon":-88.223450,"nic":8,"rc":186,"seen_pos":1.947,"r_dst":123.243,"r_dir":275.3,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":30577,"seen":0.2,"rssi":-16.6},
{"hex":"a2b157","type":"adsb_icao","flight":"UAL2386 ","alt_baro":37000,"alt_geom":38550,"gs":513.0,"track":90.45,"baro_rate":64,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":96.33,"lat":39.839591,"lon":-88.095459,"nic":8,"rc":186,"seen_pos":27.158,"r_dst":144.030,"r_dir":236.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":857,"seen":24.6,"rssi":-28.8},
{"hex":"a36661","type":"adsb_icao","flight":"AAL556 ","alt_baro":37000,"alt_geom":38375,"gs":501.8,"track":79.90,"baro_rate":0,"squawk":"1376","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":83.67,"lat":41.604713,"lon":-88.072858,"nic":8,"rc":186,"seen_pos":0.429,"r_dst":118.337,"r_dir":282.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":3996,"seen":0.2,"rssi":-14.8},
{"hex":"a7b4f2","type":"adsb_icao","flight":"DAL694 ","alt_baro":30000,"alt_geom":31025,"gs":436.3,"track":246.78,"baro_rate":0,"squawk":"7420","emergency":"none","category":"A4","nav_qnh":1012.8,"nav_altitude_mcp":30016,"nav_heading":248.91,"lat":42.187733,"lon":-88.058677,"nic":8,"rc":186,"seen_pos":3.773,"r_dst":129.019,"r_dir":298.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13606,"seen":1.2,"rssi":-22.3},
{"hex":"c050c0","type":"adsb_icao","flight":"ACA993 ","alt_baro":34025,"alt_geom":35425,"gs":428.8,"track":227.27,"baro_rate":0,"squawk":"0504","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":234.14,"lat":40.269196,"lon":-87.920105,"nic":8,"rc":186,"seen_pos":0.346,"r_dst":123.585,"r_dir":243.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":22572,"seen":0.3,"rssi":-21.5},
{"hex":"a05e02","type":"adsb_icao","flight":"UAL248 ","alt_baro":35000,"alt_geom":36275,"gs":482.6,"track":85.36,"baro_rate":0,"squawk":"3774","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":35008,"nav_heading":89.30,"lat":42.224030,"lon":-87.908852,"nic":8,"rc":186,"seen_pos":0.514,"r_dst":124.119,"r_dir":300.4,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2638,"seen":0.3,"rssi":-22.4},
{"hex":"a29af3","type":"adsb_icao","flight":"N267WA ","alt_baro":37000,"alt_geom":38375,"gs":517.6,"track":134.69,"geom_rate":64,"squawk":"1615","emergency":"none","category":"A3","lat":41.415115,"lon":-87.893933,"nic":8,"rc":186,"seen_pos":0.072,"r_dst":108.707,"r_dir":277.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":4972,"seen":0.1,"rssi":-16.8},
{"hex":"ac4ace","type":"adsb_icao","flight":"UAL2747 ","alt_baro":20525,"alt_geom":21250,"gs":414.0,"track":179.58,"baro_rate":1984,"squawk":"3157","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":35008,"lat":41.234756,"lon":-87.892029,"nic":8,"rc":186,"seen_pos":0.504,"r_dst":108.045,"r_dir":271.8,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":939,"seen":0.5,"rssi":-14.4},
{"hex":"a86674","type":"adsb_icao","flight":"JIA9974 ","alt_baro":33000,"alt_geom":34400,"gs":454.7,"track":176.85,"geom_rate":64,"squawk":"3203","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":32992,"nav_heading":183.52,"lat":40.272354,"lon":-87.862061,"nic":8,"rc":186,"seen_pos":0.280,"r_dst":121.148,"r_dir":243.3,"version":2,"nic_baro":1,"nac_p":11,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":7921,"seen":0.3,"rssi":-19.3},
{"hex":"a2f95c","type":"adsb_icao","flight":"ASA201 ","alt_baro":32000,"alt_geom":33450,"gs":432.3,"track":272.25,"baro_rate":0,"squawk":"7046","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32000,"nav_heading":277.73,"lat":38.810601,"lon":-87.845764,"nic":8,"rc":186,"seen_pos":27.723,"r_dst":179.695,"r_dir":217.7,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":10967,"seen":4.3,"rssi":-30.2},
{"hex":"ac42df","type":"adsb_icao","flight":"SWA2205 ","alt_baro":35000,"alt_geom":36450,"gs":512.2,"track":88.43,"baro_rate":0,"squawk":"7302","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":35008,"nav_heading":94.22,"lat":40.166794,"lon":-87.826965,"nic":8,"rc":186,"seen_pos":1.469,"r_dst":122.896,"r_dir":240.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2182,"seen":0.8,"rssi":-17.5},
{"hex":"ac247a","type":"adsb_icao","flight":"SWA2557 ","alt_baro":34000,"alt_geom":35600,"gs":418.8,"track":275.34,"baro_rate":0,"squawk":"5613","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":265.08,"lat":38.620834,"lon":-87.710127,"nic":8,"rc":186,"seen_pos":3.197,"r_dst":185.517,"r_dir":214.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8247,"seen":1.8,"rssi":-27.8},
{"hex":"abd1e3","type":"adsb_icao","flight":"SWA2777 ","alt_baro":22350,"alt_geom":23200,"gs":425.2,"track":174.20,"baro_rate":2048,"squawk":"6573","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":31008,"nav_heading":189.84,"lat":40.991867,"lon":-87.697510,"nic":8,"rc":186,"seen_pos":6.980,"r_dst":100.242,"r_dir":263.4,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1060,"seen":7.0,"rssi":-26.5},
{"hex":"c0654b","type":"adsb_icao","flight":"ACA742 ","alt_baro":39000,"alt_geom":40300,"gs":509.4,"track":76.84,"baro_rate":0,"squawk":"3644","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":39008,"nav_heading":82.27,"lat":42.738818,"lon":-87.657868,"nic":8,"rc":186,"seen_pos":0.275,"r_dst":133.300,"r_dir":314.4,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2194,"seen":0.3,"rssi":-24.9},
{"hex":"ac370b","type":"adsb_icao","flight":"SWA976 ","alt_baro":36000,"alt_geom":37675,"gs":410.4,"track":272.37,"baro_rate":0,"squawk":"0520","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":272.11,"lat":38.803106,"lon":-87.607117,"nic":8,"rc":186,"seen_pos":0.652,"r_dst":173.705,"r_dir":214.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":9928,"seen":0.1,"rssi":-20.8},
{"hex":"acd2be","type":"adsb_icao","flight":"SWA4822 ","alt_baro":33000,"alt_geom":34425,"gs":501.0,"track":70.77,"baro_rate":0,"squawk":"1161","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32992,"nav_heading":73.12,"lat":39.171427,"lon":-87.576782,"nic":8,"rc":186,"seen_pos":0.399,"r_dst":154.819,"r_dir":218.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1538,"seen":0.2,"rssi":-26.3},
{"hex":"abd4e7","type":"adsb_icao","flight":"SWA742 ","alt_baro":30000,"alt_geom":31025,"gs":440.2,"track":264.26,"baro_rate":0,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":30016,"nav_heading":277.73,"lat":43.056839,"lon":-87.517601,"nic":8,"rc":186,"seen_pos":0.125,"r_dst":142.985,"r_dir":321.7,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5888,"seen":0.1,"rssi":-26.7},
{"hex":"a1fb1a","type":"adsb_icao","alt_baro":37050,"alt_geom":38675,"gs":516.6,"track":59.91,"baro_rate":64,"lat":37.781708,"lon":-87.506220,"nic":8,"rc":186,"seen_pos":46.795,"r_dst":225.511,"r_dir":205.0,"version":0,"nac_p":8,"nac_v":2,"sil":2,"sil_type":"unknown","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":101,"seen":44.1,"rssi":-31.4},
{"hex":"a39338","type":"adsb_icao","flight":"JBU135 ","alt_baro":36000,"alt_geom":37475,"gs":400.3,"track":259.05,"geom_rate":0,"squawk":"7104","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":36000,"nav_heading":261.56,"lat":40.485672,"lon":-87.485229,"nic":8,"rc":186,"seen_pos":1.348,"r_dst":99.957,"r_dir":245.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":33319,"seen":0.2,"rssi":-13.0},
{"hex":"a7ea86","type":"adsb_icao","flight":"SKW5606 ","alt_baro":10100,"alt_geom":10425,"gs":344.8,"track":303.04,"baro_rate":-1408,"squawk":"0574","emergency":"none","category":"A3","nav_qnh":1018.4,"nav_altitude_mcp":10016,"nav_modes":["autopilot","tcas"],"lat":41.674867,"lon":-87.369413,"nic":8,"rc":186,"seen_pos":6.022,"r_dst":88.762,"r_dir":289.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":18971,"seen":1.3,"rssi":-27.4},
{"hex":"a7ccce","type":"adsb_icao","alt_baro":12200,"alt_geom":12600,"gs":313.0,"track":90.37,"baro_rate":1536,"nav_qnh":1018.4,"nav_altitude_mcp":23008,"nav_modes":["autopilot","vnav","tcas"],"lat":41.947244,"lon":-87.363217,"nic":8,"rc":186,"seen_pos":5.167,"r_dst":94.838,"r_dir":298.7,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":51,"seen":5.2,"rssi":-27.4},
{"hex":"a254da","type":"adsb_icao","flight":"UAL2480 ","alt_baro":37000,"alt_geom":38425,"gs":532.8,"track":86.77,"geom_rate":0,"squawk":"6314","emergency":"none","category":"A5","nav_qnh":1012.8,"nav_altitude_mcp":36992,"nav_heading":92.81,"nav_modes":["autopilot","vnav","lnav","tcas"],"lat":41.880112,"lon":-87.359224,"nic":8,"rc":186,"seen_pos":0.276,"r_dst":92.887,"r_dir":296.5,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5560,"seen":0.3,"rssi":-13.8},
{"hex":"a272bf","type":"adsb_icao","flight":"AAY231 ","alt_baro":29650,"alt_geom":30900,"gs":455.7,"track":161.05,"baro_rate":1408,"squawk":"6534","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":32992,"lat":40.415908,"lon":-87.317588,"nic":8,"rc":186,"seen_pos":2.019,"r_dst":95.196,"r_dir":240.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":4338,"seen":0.6,"rssi":-15.2},
{"hex":"a7e691","type":"adsb_icao","flight":"SKW6230 ","alt_baro":11100,"alt_geom":11400,"gs":345.5,"track":301.99,"geom_rate":-2304,"squawk":"6776","emergency":"none","category":"A2","nav_qnh":1019.2,"nav_altitude_mcp":10016,"lat":41.615047,"lon":-87.212539,"nic":8,"rc":186,"seen_pos":0.183,"r_dst":80.967,"r_dir":288.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":21889,"seen":0.2,"rssi":-22.5},
{"hex":"abadc0","type":"adsb_icao","flight":"SWA3810 ","alt_baro":39000,"alt_geom":40775,"gs":499.6,"track":92.75,"baro_rate":0,"squawk":"1154","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":39008,"nav_heading":101.25,"lat":38.789246,"lon":-87.200098,"nic":8,"rc":186,"seen_pos":0.476,"r_dst":164.735,"r_dir":208.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2460,"seen":0.2,"rssi":-20.1},
{"hex":"c01bc8","type":"adsb_icao","flight":"TOR800 ","alt_baro":15550,"alt_geom":16100,"gs":360.5,"track":99.10,"geom_rate":2368,"squawk":"3145","emergency":"none","category":"A2","nav_heading":101.95,"lat":41.776164,"lon":-87.181049,"nic":8,"rc":186,"seen_pos":1.963,"r_dst":83.040,"r_dir":295.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":402,"seen":1.2,"rssi":-16.7},
{"hex":"aca5e5","type":"adsb_icao","flight":"DAL2918 ","alt_baro":29000,"alt_geom":30025,"gs":489.8,"track":110.43,"baro_rate":0,"squawk":"3663","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":28992,"nav_heading":113.91,"lat":42.875449,"lon":-87.169974,"nic":8,"rc":186,"seen_pos":4.560,"r_dst":124.958,"r_dir":324.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":721,"seen":0.2,"rssi":-25.1},
{"hex":"a7bf22","type":"adsb_icao","flight":"DAL683 ","alt_baro":33000,"alt_geom":34475,"gs":522.3,"track":78.07,"baro_rate":0,"squawk":"7624","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32992,"nav_heading":0.00,"lat":40.015549,"lon":-87.168274,"nic":8,"rc":186,"seen_pos":1.037,"r_dst":104.284,"r_dir":227.4,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":3630,"seen":0.3,"rssi":-12.4},
{"hex":"a1b4b2","type":"adsb_icao","flight":"AAY1278 ","alt_baro":35000,"alt_geom":36575,"gs":455.6,"track":150.11,"baro_rate":64,"squawk":"1303","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":35008,"lat":39.297592,"lon":-87.152989,"nic":8,"rc":186,"seen_pos":0.225,"r_dst":137.263,"r_dir":214.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":9805,"seen":0.2,"rssi":-15.8},
{"hex":"a05671","type":"adsb_icao","flight":"AAL2779 ","alt_baro":37000,"alt_geom":38750,"gs":408.0,"track":171.97,"baro_rate":0,"squawk":"3260","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":36992,"lat":38.821289,"lon":-87.151018,"nic":8,"rc":186,"seen_pos":0.676,"r_dst":161.965,"r_dir":208.5,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":11436,"seen":0.2,"rssi":-16.0},
{"hex":"a438b0","type":"adsb_icao","flight":"DAL1207 ","alt_baro":32000,"alt_geom":33525,"gs":432.8,"track":225.56,"baro_rate":0,"squawk":"4121","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32000,"nav_heading":0.00,"lat":38.442466,"lon":-87.103699,"nic":8,"rc":186,"seen_pos":2.235,"r_dst":181.556,"r_dir":204.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":24748,"seen":0.2,"rssi":-24.4},
{"hex":"a355b1","type":"adsb_icao","flight":"ENY3454 ","alt_baro":12200,"alt_geom":12625,"gs":357.9,"track":305.33,"baro_rate":-1600,"squawk":"1005","emergency":"none","category":"A3","nav_qnh":1018.4,"nav_altitude_mcp":11008,"nav_modes":["autopilot","vnav","tcas"],"lat":41.307999,"lon":-87.087463,"nic":8,"rc":186,"seen_pos":0.456,"r_dst":71.925,"r_dir":275.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13069,"seen":0.3,"rssi":-13.8},
{"hex":"a94b2b","type":"adsb_icao","flight":"UAL1843 ","alt_baro":35000,"alt_geom":36350,"gs":492.0,"track":89.88,"baro_rate":0,"squawk":"0616","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_fms":35008,"lat":41.160371,"lon":-87.036376,"nic":8,"rc":186,"seen_pos":0.318,"r_dst":69.476,"r_dir":268.3,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5901,"seen":0.2,"rssi":-8.1},
{"hex":"a15040","type":"adsb_icao","flight":"GTW184 ","alt_baro":24875,"alt_geom":25900,"gs":293.8,"track":259.61,"baro_rate":1536,"squawk":"7353","emergency":"none","category":"A1","nav_qnh":1013.6,"nav_altitude_mcp":28000,"lat":39.666504,"lon":-87.004673,"nic":9,"rc":75,"seen_pos":1.417,"r_dst":115.115,"r_dir":217.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1644,"seen":0.7,"rssi":-17.8},
{"hex":"a271de","type":"adsb_icao","flight":"257DV ","alt_baro":16825,"alt_geom":17350,"gs":394.1,"track":46.95,"geom_rate":-128,"squawk":"5356","emergency":"none","category":"A2","nav_qnh":1019.2,"nav_altitude_mcp":16992,"nav_heading":48.52,"lat":41.897827,"lon":-86.966463,"nic":8,"rc":186,"seen_pos":0.759,"r_dst":77.952,"r_dir":302.8,"version":2,"nic_baro":1,"nac_p":11,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1077,"seen":0.2,"rssi":-21.1},
{"hex":"a4b64b","type":"adsb_icao","flight":"RPA4774 ","alt_baro":16475,"alt_geom":17000,"gs":374.0,"track":249.82,"baro_rate":-1920,"squawk":"3456","emergency":"none","category":"A3","nav_qnh":1018.4,"nav_altitude_mcp":11008,"nav_modes":["autopilot","vnav","tcas"],"lat":42.505964,"lon":-86.926958,"nic":8,"rc":186,"seen_pos":0.919,"r_dst":100.904,"r_dir":321.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8783,"seen":0.9,"rssi":-24.5},
{"hex":"ac61db","type":"adsb_icao","flight":"SWA3387 ","alt_baro":32000,"alt_geom":33425,"gs":429.5,"track":254.05,"baro_rate":0,"squawk":"1036","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32000,"nav_heading":258.05,"lat":39.643753,"lon":-86.926216,"nic":8,"rc":186,"seen_pos":0.466,"r_dst":114.135,"r_dir":215.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":17784,"seen":0.5,"rssi":-19.5},
{"hex":"a697db","type":"adsb_icao","flight":"ASA7 ","alt_baro":34000,"alt_geom":35500,"gs":422.3,"track":267.83,"baro_rate":0,"squawk":"3602","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":267.89,"lat":39.024445,"lon":-86.913797,"nic":8,"rc":186,"seen_pos":0.169,"r_dst":146.086,"r_dir":206.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":10793,"seen":0.2,"rssi":-17.1},
{"hex":"a336df","type":"adsb_icao","flight":"AAL627 ","alt_baro":16975,"alt_geom":17600,"gs":402.3,"track":308.24,"baro_rate":-2560,"squawk":"7165","emergency":"none","category":"A3","nav_qnh":1018.4,"nav_altitude_mcp":11008,"nav_heading":312.89,"lat":41.194473,"lon":-86.908447,"nic":8,"rc":186,"seen_pos":1.061,"r_dst":63.631,"r_dir":269.9,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":16352,"seen":1.1,"rssi":-11.5},
{"hex":"ac24c5","type":"adsb_icao","flight":"SWA4852 ","alt_baro":37000,"alt_geom":38775,"gs":504.9,"track":80.54,"baro_rate":0,"squawk":"6215","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":61.17,"lat":38.469699,"lon":-86.875671,"nic":8,"rc":186,"seen_pos":3.273,"r_dst":176.002,"r_dir":201.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2819,"seen":1.0,"rssi":-25.6},
{"hex":"a35d79","type":"adsb_icao","flight":"DAL2445 ","alt_baro":35475,"alt_geom":37200,"gs":502.1,"track":74.16,"geom_rate":832,"squawk":"7213","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":36000,"nav_heading":0.00,"lat":38.347916,"lon":-86.861650,"nic":8,"rc":186,"seen_pos":12.925,"r_dst":182.638,"r_dir":200.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2524,"seen":0.7,"rssi":-28.3},
{"hex":"abaaa8","type":"adsb_icao","flight":"SWA2679 ","alt_baro":36000,"alt_geom":37575,"gs":396.2,"track":252.07,"baro_rate":64,"squawk":"3021","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":258.75,"lat":39.716217,"lon":-86.768766,"nic":8,"rc":186,"seen_pos":0.287,"r_dst":106.491,"r_dir":213.4,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":23878,"seen":0.3,"rssi":-23.1},
{"hex":"a3e9c5","type":"adsb_icao","flight":"DAL1484 ","alt_baro":29000,"alt_geom":30000,"gs":476.4,"track":96.87,"baro_rate":0,"squawk":"2463","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":28992,"nav_heading":0.00,"lat":43.449019,"lon":-86.759949,"nic":8,"rc":186,"seen_pos":16.284,"r_dst":145.927,"r_dir":337.9,"version":0,"nic_baro":1,"nac_p":8,"nac_v":2,"sil":2,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":143,"seen":13.3,"rssi":-29.4},
{"hex":"ab6339","type":"adsb_icao","flight":"SWA1782 ","alt_baro":9850,"alt_geom":10100,"gs":343.0,"track":269.33,"baro_rate":0,"squawk":"7451","emergency":"none","category":"A3","nav_qnh":1019.2,"nav_altitude_mcp":6016,"nav_heading":276.33,"lat":41.520965,"lon":-86.719623,"nic":8,"rc":186,"seen_pos":0.413,"r_dst":58.160,"r_dir":289.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":12813,"seen":0.1,"rssi":-10.9},
{"hex":"a09852","type":"adsb_icao","flight":"UAL1023 ","alt_baro":24850,"alt_geom":25750,"gs":462.5,"track":87.40,"baro_rate":1856,"squawk":"7204","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":31008,"nav_heading":92.11,"lat":42.125911,"lon":-86.687494,"nic":8,"rc":186,"seen_pos":0.298,"r_dst":76.811,"r_dir":316.5,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1680,"seen":0.2,"rssi":-11.0},
{"hex":"a2919a","type":"adsb_icao","flight":"ASA379 ","alt_baro":32000,"alt_geom":33350,"gs":432.9,"track":296.92,"baro_rate":0,"squawk":"0551","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32000,"nav_heading":284.77,"lat":40.026033,"lon":-86.631754,"nic":8,"rc":186,"seen_pos":1.208,"r_dst":87.545,"r_dir":216.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":11263,"seen":0.7,"rssi":-12.2},
{"hex":"a98051","type":"adsb_icao","flight":"FFT3175 ","alt_baro":27975,"alt_geom":29200,"gs":449.7,"track":235.14,"baro_rate":0,"squawk":"7415","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":28000,"lat":39.991352,"lon":-86.622453,"nic":8,"rc":186,"seen_pos":1.381,"r_dst":88.999,"r_dir":215.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":3,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":18836,"seen":0.4,"rssi":-9.5},
{"hex":"abfefd","type":"adsb_icao","alt_baro":15450,"alt_geom":15950,"gs":366.7,"track":255.47,"baro_rate":-1408,"category":"A3","nav_qnh":1018.4,"nav_altitude_mcp":11008,"nav_heading":262.97,"lat":42.580536,"lon":-86.619894,"nic":8,"rc":186,"seen_pos":32.072,"r_dst":96.616,"r_dir":329.2,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":11534,"seen":0.8,"rssi":-28.8},
{"hex":"aab922","type":"adsb_icao","flight":"ENY4275 ","alt_baro":31000,"alt_geom":32400,"gs":481.8,"track":119.33,"baro_rate":64,"squawk":"6553","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":31008,"nav_modes":["autopilot","althold","tcas"],"lat":39.665951,"lon":-86.569702,"nic":8,"rc":186,"seen_pos":1.722,"r_dst":104.494,"r_dir":208.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8893,"seen":0.2,"rssi":-13.2},
{"hex":"aaf253","type":"adsb_icao","flight":"SKW3780 ","alt_baro":21000,"alt_geom":21750,"gs":451.1,"track":125.98,"geom_rate":0,"squawk":"2403","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":20992,"nav_heading":132.19,"lat":41.723282,"lon":-86.540430,"nic":8,"rc":186,"seen_pos":1.532,"r_dst":56.240,"r_dir":304.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":6651,"seen":0.3,"rssi":-11.7},
{"hex":"abadd6","type":"adsb_icao","flight":"SWA2136 ","alt_baro":9450,"alt_geom":9675,"gs":288.1,"track":265.02,"baro_rate":3648,"squawk":"4077","emergency":"none","category":"A3","nav_qnh":1016.0,"nav_altitude_mcp":27008,"nav_heading":270.70,"lat":39.756134,"lon":-86.513214,"nic":8,"rc":186,"seen_pos":3.309,"r_dst":98.486,"r_dir":208.4,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":91,"seen":1.1,"rssi":-28.0},
{"hex":"a2e28f","type":"adsb_icao","flight":"SWA4248 ","alt_baro":23575,"alt_geom":24425,"gs":405.7,"track":84.77,"baro_rate":2112,"squawk":"1330","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":39008,"nav_heading":89.30,"lat":41.964249,"lon":-86.487184,"nic":8,"rc":186,"seen_pos":0.269,"r_dst":63.626,"r_dir":316.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2805,"seen":0.3,"rssi":-11.8},
{"hex":"a494c5","type":"adsb_icao","flight":"UAL733 ","alt_baro":33000,"alt_geom":34175,"gs":505.3,"track":92.04,"baro_rate":0,"squawk":"2651","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32992,"nav_heading":94.22,"lat":42.804969,"lon":-86.462722,"nic":8,"rc":186,"seen_pos":1.638,"r_dst":105.276,"r_dir":336.2,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1483,"seen":1.0,"rssi":-23.3},
{"hex":"a4fe01","type":"adsb_icao","flight":"N4200K ","alt_baro":43000,"alt_geom":44725,"gs":336.2,"track":216.29,"baro_rate":0,"squawk":"7236","emergency":"none","category":"A2","nav_qnh":1013.6,"nav_altitude_mcp":43008,"lat":40.480911,"lon":-86.426147,"nic":8,"rc":186,"seen_pos":3.456,"r_dst":60.459,"r_dir":224.4,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":20055,"seen":0.3,"rssi":-10.0},
{"hex":"a3a509","type":"adsb_icao","flight":"DAL314 ","alt_baro":37000,"alt_geom":38500,"gs":501.7,"track":77.10,"geom_rate":0,"squawk":"1142","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":36992,"nav_heading":83.67,"lat":40.779968,"lon":-86.372559,"nic":8,"rc":186,"seen_pos":0.532,"r_dst":47.035,"r_dir":237.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":10457,"seen":0.4,"rssi":-4.5},
{"hex":"a3eeb0","type":"adsb_icao","flight":"UAL1764 ","alt_baro":36000,"alt_geom":37725,"gs":417.4,"track":283.44,"baro_rate":0,"squawk":"6275","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":281.25,"lat":38.769379,"lon":-86.351823,"nic":8,"rc":186,"seen_pos":2.437,"r_dst":151.345,"r_dir":195.3,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8898,"seen":0.1,"rssi":-21.3},
{"hex":"ac733b","type":"adsb_icao","flight":"DAL2022 ","alt_baro":34000,"alt_geom":35475,"gs":415.2,"track":265.72,"baro_rate":64,"squawk":"2774","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":270.00,"lat":40.081011,"lon":-86.339930,"nic":8,"rc":186,"seen_pos":0.213,"r_dst":77.534,"r_dir":209.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":19880,"seen":0.2,"rssi":-9.2},
{"hex":"a33aa7","type":"adsb_icao","flight":"EDV4935 ","alt_baro":35025,"alt_geom":36625,"gs":384.0,"track":184.18,"geom_rate":0,"squawk":"2130","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":35008,"nav_heading":90.00,"lat":39.751096,"lon":-86.339233,"nic":8,"rc":186,"seen_pos":0.177,"r_dst":95.290,"r_dir":204.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":12555,"seen":0.2,"rssi":-19.1},
{"hex":"a35e6f","type":"adsb_icao","flight":"EDV5244 ","alt_baro":35000,"alt_geom":36225,"gs":498.8,"track":100.16,"geom_rate":0,"squawk":"3610","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":35008,"nav_heading":105.47,"lat":43.375671,"lon":-86.280489,"nic":8,"rc":186,"seen_pos":2.712,"r_dst":134.910,"r_dir":345.4,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1096,"seen":0.3,"rssi":-19.6},
{"hex":"aa466f","type":"adsb_icao","flight":"SKW4931 ","alt_baro":23000,"alt_geom":23850,"gs":469.2,"track":73.53,"geom_rate":0,"squawk":"3107","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":23008,"lat":42.840349,"lon":-86.270992,"nic":8,"rc":186,"seen_pos":0.586,"r_dst":104.084,"r_dir":341.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1365,"seen":0.6,"rssi":-21.9},
{"hex":"a4350c","type":"adsb_icao","flight":"DAL1441 ","alt_baro":32975,"alt_geom":34425,"gs":514.0,"track":42.16,"baro_rate":0,"squawk":"0620","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32992,"nav_heading":0.00,"lat":39.958557,"lon":-86.255554,"nic":8,"rc":186,"seen_pos":0.562,"r_dst":82.340,"r_dir":205.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":7771,"seen":0.2,"rssi":-15.2},
{"hex":"ac47de","type":"adsb_icao","flight":"SWA3972 ","alt_baro":31000,"alt_geom":32400,"gs":499.6,"track":71.57,"baro_rate":0,"squawk":"1101","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":31008,"nav_heading":75.94,"lat":39.526337,"lon":-86.238496,"nic":8,"rc":186,"seen_pos":2.314,"r_dst":106.251,"r_dir":198.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":4274,"seen":0.1,"rssi":-24.7},
{"hex":"a4e181","type":"adsb_icao","flight":"RPA9931 ","alt_baro":15350,"alt_geom":15925,"gs":354.0,"track":179.68,"baro_rate":2368,"squawk":"4070","emergency":"none","category":"A3","nav_qnh":1016.0,"nav_altitude_mcp":25024,"nav_modes":["autopilot","vnav","tcas"],"lat":39.398026,"lon":-86.232764,"nic":8,"rc":186,"seen_pos":6.518,"r_dst":113.513,"r_dir":197.4,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":141,"seen":3.6,"rssi":-27.9},
{"hex":"aa31ed","type":"adsb_icao","flight":"SKW6438 ","alt_baro":21000,"alt_geom":21675,"gs":427.1,"track":255.77,"geom_rate":-1536,"squawk":"7435","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":11008,"lat":42.654745,"lon":-86.225238,"nic":8,"rc":186,"seen_pos":1.460,"r_dst":92.925,"r_dir":339.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8312,"seen":0.8,"rssi":-23.7},
{"hex":"a05827","type":"adsb_icao","flight":"RPA4430 ","alt_baro":25000,"alt_geom":25950,"gs":426.6,"track":283.42,"baro_rate":-64,"squawk":"6675","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":18208,"nav_modes":["autopilot","vnav","tcas"],"lat":41.314087,"lon":-86.205098,"nic":8,"rc":186,"seen_pos":0.277,"r_dst":32.503,"r_dir":282.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13306,"seen":0.2,"rssi":-5.9},
{"hex":"a3244f","type":"adsb_icao","flight":"AAL2768 ","alt_baro":23000,"alt_geom":23875,"gs":452.8,"track":308.36,"baro_rate":0,"squawk":"6677","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":11008,"nav_heading":314.30,"lat":40.774081,"lon":-86.196608,"nic":8,"rc":186,"seen_pos":0.156,"r_dst":40.786,"r_dir":231.0,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13664,"seen":0.2,"rssi":-3.5},
{"hex":"abef98","type":"adsb_icao","alt_geom":39650,"gs":411.0,"track":150.88,"baro_rate":1216,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":40992,"nav_heading":156.80,"lat":38.059799,"lon":-86.132637,"nic":8,"rc":186,"seen_pos":7.747,"r_dst":191.039,"r_dir":189.0,"version":0,"nic_baro":1,"nac_p":8,"nac_v":2,"sil":2,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":498,"seen":7.7,"rssi":-29.6},
{"hex":"a4c031","type":"adsb_icao","flight":"LYC405 ","alt_baro":26975,"alt_geom":28000,"gs":312.3,"track":145.70,"baro_rate":0,"squawk":"7237","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":27008,"lat":42.025131,"lon":-86.124136,"nic":8,"rc":186,"seen_pos":0.671,"r_dst":56.699,"r_dir":330.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5489,"seen":0.2,"rssi":-9.2},
{"hex":"abfcb6","type":"adsb_icao","flight":"SWA4272 ","alt_baro":25650,"alt_geom":26625,"gs":460.9,"track":86.39,"baro_rate":1408,"squawk":"3264","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":92.11,"lat":41.787781,"lon":-86.112775,"nic":8,"rc":186,"seen_pos":0.122,"r_dst":44.580,"r_dir":322.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":4058,"seen":0.1,"rssi":-7.3},
{"hex":"a2b47f","type":"adsb_icao","flight":"UAL560 ","alt_baro":36000,"alt_geom":37150,"gs":422.6,"track":294.02,"baro_rate":0,"squawk":"2732","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":295.31,"lat":43.792295,"lon":-85.999865,"nic":8,"rc":186,"seen_pos":0.624,"r_dst":156.962,"r_dir":352.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":9747,"seen":0.3,"rssi":-22.9},
{"hex":"ac0f4d","type":"adsb_icao","flight":"SWA36 ","alt_baro":39000,"alt_geom":40800,"gs":476.5,"track":119.69,"baro_rate":0,"squawk":"3120","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":39008,"nav_heading":130.78,"lat":39.393356,"lon":-85.947120,"nic":8,"rc":186,"seen_pos":0.726,"r_dst":110.632,"r_dir":190.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":11775,"seen":0.1,"rssi":-17.3},
{"hex":"a5e3e4","type":"adsb_icao","flight":"EDV4986 ","alt_baro":26925,"alt_geom":27875,"gs":409.6,"track":277.58,"geom_rate":-1024,"squawk":"2755","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":14016,"nav_heading":285.47,"lat":43.156671,"lon":-85.921129,"nic":8,"rc":186,"seen_pos":1.475,"r_dst":118.719,"r_dir":351.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":9567,"seen":0.5,"rssi":-23.4},
{"hex":"a2b12e","type":"adsb_icao","flight":"UAL685 ","alt_baro":31000,"alt_geom":32300,"gs":507.6,"track":39.08,"baro_rate":0,"squawk":"1752","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":24000,"nav_heading":42.89,"lat":40.304764,"lon":-85.882874,"nic":8,"rc":186,"seen_pos":0.076,"r_dst":56.738,"r_dir":198.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":7682,"seen":0.1,"rssi":-8.0},
{"hex":"a788d3","type":"adsb_icao","flight":"DAL785 ","alt_baro":30100,"alt_geom":31350,"gs":518.9,"track":67.09,"baro_rate":-960,"squawk":"1611","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":24000,"lat":40.672577,"lon":-85.877686,"nic":8,"rc":186,"seen_pos":0.483,"r_dst":36.222,"r_dir":208.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":3,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8409,"seen":0.3,"rssi":-4.9},
{"hex":"abd57e","type":"adsb_icao","flight":"SWA2792 ","alt_baro":39000,"alt_geom":40850,"gs":450.8,"track":137.25,"baro_rate":0,"squawk":"3161","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":39008,"nav_heading":146.25,"lat":38.586502,"lon":-85.853915,"nic":8,"rc":186,"seen_pos":1.336,"r_dst":158.008,"r_dir":186.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":11428,"seen":0.7,"rssi":-18.3},
{"hex":"ab2372","type":"adsb_icao","flight":"AAL1372 ","alt_baro":23625,"alt_geom":24650,"gs":481.6,"track":310.54,"baro_rate":-704,"squawk":"7147","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":23008,"lat":40.502821,"lon":-85.846044,"nic":8,"rc":186,"seen_pos":0.356,"r_dst":44.945,"r_dir":200.6,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":9178,"seen":0.3,"rssi":-4.3},
{"hex":"a74fa2","type":"adsb_icao","alt_baro":35000,"alt_geom":36800,"gs":498.1,"track":84.70,"geom_rate":64,"category":"A3","lat":38.084518,"lon":-85.842668,"nic":8,"rc":186,"seen_pos":2.223,"r_dst":187.972,"r_dir":184.9,"version":2,"nic_baro":1,"nac_p":11,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":237,"seen":0.2,"rssi":-29.4},
{"hex":"abb9df","type":"adsb_icao","flight":"SWA924 ","alt_baro":34000,"alt_geom":35625,"gs":421.8,"track":273.53,"baro_rate":0,"squawk":"6636","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":272.81,"lat":38.731834,"lon":-85.828369,"nic":8,"rc":186,"seen_pos":0.160,"r_dst":149.206,"r_dir":185.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5010,"seen":0.2,"rssi":-17.7},
{"hex":"ad5a54","type":"adsb_icao","flight":"N96FK ","alt_baro":1750,"alt_geom":1775,"gs":122.0,"track":90.47,"baro_rate":-256,"squawk":"1200","emergency":"none","category":"A1","nav_qnh":1016.8,"nav_altitude_mcp":1792,"nav_heading":87.89,"nav_modes":[],"lat":41.501816,"lon":-85.799914,"nic":9,"rc":75,"seen_pos":39.583,"r_dst":22.409,"r_dir":323.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":316,"seen":34.0,"rssi":-29.9},
{"hex":"ac6a1f","type":"adsb_icao","flight":"SWA2660 ","alt_baro":28200,"alt_geom":29500,"gs":468.4,"track":349.79,"baro_rate":-1024,"squawk":"1627","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":28000,"nav_heading":348.75,"lat":39.387942,"lon":-85.789612,"nic":8,"rc":186,"seen_pos":0.075,"r_dst":109.849,"r_dir":187.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2582,"seen":0.1,"rssi":-13.5},
{"hex":"a29e5c","type":"adsb_icao","flight":"SKW4109 ","alt_baro":12250,"alt_geom":12600,"gs":318.1,"track":298.34,"baro_rate":-1472,"squawk":"6075","emergency":"none","category":"A3","nav_qnh":1017.6,"nav_altitude_mcp":4000,"nav_modes":["autopilot","vnav","tcas"],"lat":41.284515,"lon":-85.690613,"nic":8,"rc":186,"seen_pos":0.251,"r_dst":9.867,"r_dir":299.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":14214,"seen":0.3,"rssi":-2.4},
{"hex":"aa8bfa","type":"adsb_icao","flight":"ENY4271 ","alt_baro":27000,"alt_geom":28050,"gs":473.9,"track":97.39,"baro_rate":-64,"squawk":"3162","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":27008,"nav_modes":["autopilot","althold","tcas"],"lat":41.906158,"lon":-85.682935,"nic":8,"rc":186,"seen_pos":0.210,"r_dst":42.944,"r_dir":349.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5187,"seen":0.2,"rssi":-9.7},
{"hex":"a04aa9","type":"adsb_icao","flight":"RPA4801 ","alt_baro":32000,"alt_geom":33200,"gs":407.3,"track":272.25,"baro_rate":0,"squawk":"2724","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32000,"nav_modes":["autopilot","vnav","tcas"],"lat":41.515228,"lon":-85.672073,"nic":8,"rc":186,"seen_pos":0.067,"r_dst":20.224,"r_dir":337.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":18357,"seen":0.1,"rssi":-3.1},
{"hex":"a12ae8","type":"adsb_icao","flight":"UAL402 ","alt_baro":35000,"alt_geom":36675,"gs":540.4,"track":43.65,"baro_rate":0,"squawk":"3407","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":35008,"nav_heading":47.81,"lat":38.896362,"lon":-85.585287,"nic":8,"rc":186,"seen_pos":2.261,"r_dst":138.615,"r_dir":181.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2094,"seen":1.0,"rssi":-16.6},
{"hex":"a0b848","type":"adsb_icao","flight":"UAL8167 ","alt_baro":31975,"alt_geom":33575,"gs":424.2,"track":271.62,"baro_rate":64,"squawk":"2102","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32000,"nav_heading":0.00,"lat":38.854732,"lon":-85.582825,"nic":8,"rc":186,"seen_pos":0.354,"r_dst":141.111,"r_dir":181.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":6423,"seen":0.4,"rssi":-21.8},
{"hex":"a1ac11","type":"adsb_icao","flight":"ENY3819 ","alt_baro":28000,"alt_geom":29025,"gs":416.2,"track":256.10,"baro_rate":-64,"squawk":"3537","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":20000,"nav_modes":["autopilot","vnav","tcas"],"lat":42.773267,"lon":-85.571999,"nic":8,"rc":186,"seen_pos":1.416,"r_dst":94.266,"r_dir":358.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5116,"seen":0.6,"rssi":-14.8},
{"hex":"a47063","type":"adsb_icao","flight":"N3851L ","alt_baro":11375,"alt_geom":11700,"gs":159.3,"track":80.24,"geom_rate":64,"squawk":"1200","emergency":"none","category":"A1","lat":41.974756,"lon":-85.571935,"nic":10,"rc":25,"seen_pos":0.700,"r_dst":46.381,"r_dir":356.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8301,"seen":0.3,"rssi":-14.8},
{"hex":"a33d68","type":"adsb_icao","flight":"DAL563 ","alt_baro":38000,"alt_geom":39625,"gs":390.1,"track":265.74,"geom_rate":0,"squawk":"1667","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":38016,"nav_heading":266.48,"lat":40.743310,"lon":-85.549663,"nic":8,"rc":186,"seen_pos":0.268,"r_dst":27.760,"r_dir":184.7,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":21801,"seen":0.1,"rssi":-3.9},
{"hex":"a54169","type":"adsb_icao","flight":"LXJ438 ","alt_baro":43000,"alt_geom":44850,"gs":412.2,"track":233.19,"geom_rate":0,"squawk":"6072","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":43008,"nav_heading":240.47,"lat":39.189722,"lon":-85.536804,"nic":8,"rc":186,"seen_pos":0.423,"r_dst":120.958,"r_dir":180.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":19192,"seen":0.1,"rssi":-17.7},
{"hex":"aa8c10","type":"adsb_icao","flight":"SKW5350 ","alt_baro":30000,"alt_geom":31200,"gs":428.4,"track":300.28,"geom_rate":0,"squawk":"1335","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":24992,"lat":41.063124,"lon":-85.529750,"nic":8,"rc":186,"seen_pos":0.370,"r_dst":8.573,"r_dir":189.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13633,"seen":0.1,"rssi":-2.1},
{"hex":"a36f7e","type":"adsb_icao","flight":"DAL2578 ","alt_baro":36000,"alt_geom":37625,"gs":392.6,"track":226.55,"baro_rate":0,"squawk":"6651","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":0.00,"lat":39.654831,"lon":-85.492023,"nic":8,"rc":186,"seen_pos":1.512,"r_dst":93.022,"r_dir":179.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":14221,"seen":0.1,"rssi":-16.3},
{"hex":"aa55b5","type":"adsb_icao","flight":"SKW6245 ","alt_baro":26000,"alt_geom":27125,"gs":439.0,"track":342.23,"geom_rate":64,"squawk":"6706","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":26016,"lat":40.020074,"lon":-85.482310,"nic":8,"rc":186,"seen_pos":0.223,"r_dst":71.096,"r_dir":179.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5416,"seen":0.2,"rssi":-14.6},
{"hex":"4bb153","type":"adsb_icao","flight":"THY6627 ","alt_baro":33000,"alt_geom":34600,"gs":555.2,"track":43.76,"baro_rate":0,"squawk":"1621","emergency":"none","category":"A5","nav_qnh":1012.8,"nav_altitude_mcp":32992,"nav_heading":47.11,"lat":39.008698,"lon":-85.448018,"nic":8,"rc":186,"seen_pos":1.792,"r_dst":131.837,"r_dir":178.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1592,"seen":1.4,"rssi":-20.2},
{"hex":"aab396","type":"adsb_icao","flight":"ENY3670 ","alt_baro":31000,"alt_geom":32250,"gs":490.3,"track":91.99,"baro_rate":-64,"squawk":"6565","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":31008,"nav_modes":["autopilot","althold","tcas"],"lat":42.128632,"lon":-85.446229,"nic":8,"rc":186,"seen_pos":0.380,"r_dst":55.559,"r_dir":2.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":6543,"seen":0.4,"rssi":-8.8},
{"hex":"a426a5","type":"adsb_icao","flight":"DAL700 ","alt_baro":22300,"alt_geom":23150,"gs":449.1,"track":271.40,"baro_rate":768,"squawk":"4153","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":30016,"nav_heading":0.00,"lat":42.456246,"lon":-85.443548,"nic":8,"rc":186,"seen_pos":0.712,"r_dst":75.219,"r_dir":1.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":3633,"seen":0.4,"rssi":-8.7},
{"hex":"c06539","type":"adsb_icao","flight":"ACA595 ","alt_baro":34000,"alt_geom":35450,"gs":406.1,"track":235.33,"baro_rate":0,"squawk":"0567","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":241.88,"lat":40.771774,"lon":-85.412720,"nic":8,"rc":186,"seen_pos":0.582,"r_dst":26.259,"r_dir":171.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":18425,"seen":0.1,"rssi":-4.0},
{"hex":"4cc573","type":"adsb_icao","flight":"ICE855 ","alt_baro":32000,"alt_geom":33125,"gs":413.2,"track":218.81,"baro_rate":-64,"squawk":"0776","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32000,"nav_heading":227.81,"lat":43.431061,"lon":-85.410909,"nic":8,"rc":186,"seen_pos":0.384,"r_dst":133.764,"r_dir":1.7,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1342,"seen":0.4,"rssi":-22.0},
{"hex":"ac8abe","type":"adsb_icao","flight":"SGX907 ","alt_baro":31000,"alt_geom":32375,"gs":491.6,"track":43.76,"geom_rate":-128,"squawk":"6224","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":31008,"lat":39.066079,"lon":-85.378052,"nic":8,"rc":186,"seen_pos":1.540,"r_dst":128.492,"r_dir":177.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1651,"seen":0.1,"rssi":-24.7},
{"hex":"a41e94","type":"adsb_icao","flight":"UAL651 ","alt_baro":28000,"alt_geom":29225,"gs":469.2,"track":339.67,"baro_rate":0,"squawk":"3674","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":28000,"nav_heading":338.91,"lat":39.779984,"lon":-85.333079,"nic":8,"rc":186,"seen_pos":0.290,"r_dst":85.846,"r_dir":174.9,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":6995,"seen":0.1,"rssi":-14.0},
{"hex":"a2450b","type":"adsb_icao","flight":"UAL2148 ","alt_baro":29975,"alt_geom":31300,"gs":446.7,"track":298.92,"baro_rate":-64,"squawk":"5652","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":30016,"nav_heading":0.00,"lat":40.980698,"lon":-85.296936,"nic":8,"rc":186,"seen_pos":0.374,"r_dst":16.260,"r_dir":145.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":16993,"seen":0.1,"rssi":-2.9},
{"hex":"ab73f5","type":"adsb_icao","flight":"SKW4159 ","alt_baro":28000,"alt_geom":29025,"gs":438.8,"track":295.51,"geom_rate":-192,"squawk":"4140","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":28000,"nav_heading":284.06,"lat":43.184416,"lon":-85.274113,"nic":8,"rc":186,"seen_pos":0.553,"r_dst":119.320,"r_dir":4.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2649,"seen":0.6,"rssi":-19.3},
{"hex":"ab6101","type":"adsb_icao","flight":"AAL2028 ","alt_baro":33000,"alt_geom":34425,"gs":341.0,"track":201.15,"baro_rate":0,"squawk":"3477","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32992,"nav_heading":201.80,"lat":40.332859,"lon":-85.270198,"nic":8,"rc":186,"seen_pos":0.125,"r_dst":53.345,"r_dir":168.6,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":17577,"seen":0.1,"rssi":-8.6},
{"hex":"ab5de4","type":"adsb_icao","flight":"JUS203 ","alt_baro":1825,"alt_geom":1825,"gs":174.5,"track":195.63,"geom_rate":2944,"squawk":"6220","emergency":"none","category":"A3","lat":40.963874,"lon":-85.206278,"nic":8,"rc":186,"seen_pos":0.244,"r_dst":19.615,"r_dir":137.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":3,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":83,"seen":0.1,"rssi":-9.8},
{"hex":"aa779d","type":"adsb_icao","flight":"SWA4153 ","alt_baro":33000,"alt_geom":34200,"gs":493.2,"track":84.53,"baro_rate":0,"squawk":"6571","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32992,"nav_heading":80.16,"lat":41.888763,"lon":-85.168575,"nic":8,"rc":186,"seen_pos":0.449,"r_dst":43.719,"r_dir":19.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8704,"seen":0.1,"rssi":-3.8},
{"hex":"abd390","type":"adsb_icao","flight":"N861LH ","alt_baro":1850,"alt_geom":1875,"gs":114.2,"track":346.33,"baro_rate":-64,"squawk":"1370","emergency":"none","category":"A7","nav_qnh":1016.0,"nav_altitude_mcp":3808,"lat":40.843643,"lon":-85.151917,"nic":9,"rc":75,"seen_pos":1.958,"r_dst":26.778,"r_dir":143.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":586,"seen":0.1,"rssi":-22.7},
{"hex":"c04051","type":"adsb_icao","alt_baro":33975,"alt_geom":35050,"gs":418.2,"track":250.01,"baro_rate":0,"emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":34016,"lat":44.011743,"lon":-85.109907,"nic":8,"rc":186,"seen_pos":19.807,"r_dst":169.449,"r_dir":5.7,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":335,"seen":10.9,"rssi":-29.6},
{"hex":"c08095","type":"adsb_icao","flight":"WJA1582 ","alt_baro":26000,"alt_geom":26950,"gs":441.1,"track":122.50,"baro_rate":-2048,"squawk":"2667","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":23008,"nav_heading":130.08,"lat":43.229944,"lon":-85.058049,"nic":8,"rc":186,"seen_pos":2.310,"r_dst":123.207,"r_dir":9.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2680,"seen":0.1,"rssi":-21.3},
{"hex":"a02cf1","type":"adsb_icao","flight":"RPA9937 ","alt_baro":25000,"alt_geom":26075,"gs":435.0,"track":86.05,"baro_rate":0,"squawk":"6630","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":25024,"nav_modes":["autopilot","vnav","tcas"],"lat":39.898945,"lon":-84.951965,"nic":8,"rc":186,"seen_pos":2.296,"r_dst":82.256,"r_dir":162.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":3211,"seen":0.9,"rssi":-19.4},
{"hex":"a1d546","type":"adsb_icao","flight":"UAL46 ","alt_baro":35000,"alt_geom":36275,"gs":550.7,"track":73.87,"baro_rate":0,"squawk":"7275","emergency":"none","category":"A5","nav_qnh":1012.8,"nav_altitude_mcp":35008,"nav_heading":78.75,"lat":42.726901,"lon":-84.906304,"nic":8,"rc":186,"seen_pos":0.086,"r_dst":95.191,"r_dir":16.0,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13836,"seen":0.1,"rssi":-14.5},
{"hex":"aa6d0e","type":"adsb_icao","flight":"N771DE ","alt_baro":40000,"alt_geom":41800,"gs":377.0,"track":287.28,"baro_rate":-64,"squawk":"2523","emergency":"none","category":"A2","lat":38.763245,"lon":-84.772239,"nic":8,"rc":186,"seen_pos":0.126,"r_dst":150.328,"r_dir":166.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":805,"seen":0.1,"rssi":-23.9},
{"hex":"a0e6c6","type":"adsb_icao","flight":"UAL654 ","alt_baro":34000,"alt_geom":35500,"gs":432.4,"track":301.67,"baro_rate":64,"squawk":"3627","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":30304,"nav_heading":300.23,"lat":40.694595,"lon":-84.767822,"nic":8,"rc":186,"seen_pos":0.163,"r_dst":45.148,"r_dir":132.4,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8272,"seen":0.1,"rssi":-8.5},
{"hex":"a12a82","type":"adsb_icao","flight":"UAL1827 ","alt_baro":36025,"alt_geom":37625,"gs":535.1,"track":67.12,"baro_rate":-128,"squawk":"7630","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":75.23,"lat":40.559052,"lon":-84.755493,"nic":8,"rc":186,"seen_pos":1.403,"r_dst":51.403,"r_dir":138.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13680,"seen":0.4,"rssi":-6.6},
{"hex":"a1373d","type":"adsb_icao","flight":"DAL691 ","alt_baro":37000,"alt_geom":38375,"gs":538.4,"track":92.24,"baro_rate":0,"squawk":"3232","emergency":"none","category":"A5","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":101.95,"lat":42.454605,"lon":-84.740108,"nic":8,"rc":186,"seen_pos":0.133,"r_dst":82.416,"r_dir":24.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":12377,"seen":0.1,"rssi":-11.1},
{"hex":"a41447","type":"adsb_icao","flight":"FFT2697 ","alt_baro":38000,"alt_geom":39675,"gs":379.1,"track":238.15,"baro_rate":0,"squawk":"5743","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":38016,"lat":40.515701,"lon":-84.730042,"nic":8,"rc":186,"seen_pos":0.168,"r_dst":54.138,"r_dir":139.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":3,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":11027,"seen":0.2,"rssi":-5.1},
{"hex":"a08efd","type":"adsb_icao","flight":"AAL1483 ","alt_baro":34025,"alt_geom":35300,"gs":425.4,"track":299.58,"baro_rate":0,"squawk":"2711","emergency":"none","category":"A3","nav_altitude_mcp":34016,"nav_heading":0.00,"lat":42.478451,"lon":-84.719281,"nic":8,"rc":186,"seen_pos":0.254,"r_dst":84.103,"r_dir":24.3,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":16435,"seen":0.3,"rssi":-7.6},
{"hex":"a44964","type":"adsb_icao","flight":"UAL1792 ","alt_baro":36000,"alt_geom":37800,"gs":476.4,"track":353.13,"baro_rate":0,"squawk":"0740","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":352.27,"lat":38.446884,"lon":-84.589890,"nic":8,"rc":186,"seen_pos":2.163,"r_dst":170.783,"r_dir":165.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1127,"seen":0.5,"rssi":-23.3},
{"hex":"a6709b","type":"adsb_icao","flight":"DAL568 ","alt_baro":34000,"alt_geom":35325,"gs":411.7,"track":275.16,"baro_rate":64,"squawk":"2730","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":0.00,"lat":42.382507,"lon":-84.528298,"nic":8,"rc":186,"seen_pos":2.482,"r_dst":83.051,"r_dir":31.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8212,"seen":0.3,"rssi":-10.8},
{"hex":"a7869b","type":"adsb_icao","flight":"AAL3042 ","alt_baro":28000,"alt_geom":29175,"gs":428.6,"track":272.94,"baro_rate":0,"squawk":"7162","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":28000,"lat":41.234343,"lon":-84.501093,"nic":8,"rc":186,"seen_pos":0.527,"r_dst":45.148,"r_dir":87.4,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":12182,"seen":0.5,"rssi":-7.7},
{"hex":"a3e9b2","type":"adsb_icao","flight":"DAL1633 ","alt_baro":28000,"alt_geom":29150,"gs":445.4,"track":222.00,"baro_rate":0,"squawk":"1040","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":28000,"nav_heading":0.00,"lat":40.590134,"lon":-84.499695,"nic":8,"rc":186,"seen_pos":0.686,"r_dst":58.480,"r_dir":128.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8865,"seen":0.1,"rssi":-7.7},
{"hex":"a6c6c8","type":"adsb_icao","flight":"ASA2 ","alt_baro":37000,"alt_geom":38625,"gs":491.4,"track":108.51,"baro_rate":0,"squawk":"6665","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":118.12,"lat":40.646988,"lon":-84.468567,"nic":8,"rc":186,"seen_pos":0.268,"r_dst":57.517,"r_dir":125.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":20281,"seen":0.3,"rssi":-8.0},
{"hex":"a15743","type":"adsb_icao","flight":"KOW186 ","alt_baro":45000,"alt_geom":46600,"gs":495.9,"track":82.82,"geom_rate":0,"squawk":"3603","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":44992,"nav_heading":94.22,"lat":41.433959,"lon":-84.468320,"nic":8,"rc":186,"seen_pos":0.331,"r_dst":48.524,"r_dir":73.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":15640,"seen":0.1,"rssi":-3.5},
{"hex":"a3524e","type":"adsb_icao","flight":"DAL2294 ","alt_baro":28275,"alt_geom":29525,"gs":433.0,"track":201.83,"baro_rate":640,"squawk":"7404","emergency":"none","category":"A3","nav_altitude_mcp":30016,"nav_heading":0.00,"lat":40.298271,"lon":-84.465575,"nic":8,"rc":186,"seen_pos":0.435,"r_dst":71.915,"r_dir":138.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":9449,"seen":0.1,"rssi":-7.3},
{"hex":"a04695","type":"adsb_icao","flight":"DAL997 ","alt_baro":35000,"alt_geom":36575,"gs":464.9,"track":351.71,"baro_rate":0,"squawk":"7264","emergency":"none","category":"A3","nav_altitude_mcp":35008,"nav_heading":0.00,"lat":40.462280,"lon":-84.444458,"nic":8,"rc":186,"seen_pos":0.762,"r_dst":65.446,"r_dir":132.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":22752,"seen":0.4,"rssi":-7.3},
{"hex":"ac058a","type":"adsb_icao","flight":"DAL2454 ","alt_baro":29000,"alt_geom":30200,"gs":366.0,"track":270.63,"baro_rate":64,"squawk":"6633","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":28992,"nav_heading":16.17,"lat":40.693685,"lon":-84.434863,"nic":8,"rc":186,"seen_pos":0.386,"r_dst":57.204,"r_dir":122.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":9434,"seen":0.2,"rssi":-9.4},
{"hex":"a0b7bd","type":"adsb_icao","flight":"UAL534 ","alt_baro":31450,"alt_geom":32675,"gs":504.1,"track":100.28,"baro_rate":384,"squawk":"3545","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32992,"nav_heading":108.98,"lat":42.082152,"lon":-84.382090,"nic":8,"rc":186,"seen_pos":1.580,"r_dst":72.765,"r_dir":43.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":11583,"seen":0.4,"rssi":-6.6},
{"hex":"a8d224","type":"adsb_icao","flight":"EDV4898 ","alt_baro":22000,"alt_geom":22875,"gs":446.6,"track":201.68,"geom_rate":-128,"squawk":"4105","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":22016,"lat":40.881134,"lon":-84.342712,"nic":8,"rc":186,"seen_pos":0.267,"r_dst":55.879,"r_dir":109.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5428,"seen":0.3,"rssi":-9.0},
{"hex":"a99a52","type":"adsb_icao","flight":"FFT2279 ","alt_baro":29000,"alt_geom":30325,"gs":498.5,"track":24.66,"baro_rate":-128,"category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":28992,"lat":39.161682,"lon":-84.312724,"nic":8,"rc":186,"seen_pos":2.113,"r_dst":134.179,"r_dir":155.7,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":3,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1090,"seen":0.1,"rssi":-26.6},
{"hex":"a0540a","type":"adsb_icao","flight":"DAL490 ","alt_baro":35025,"alt_geom":36825,"gs":509.2,"track":43.73,"baro_rate":0,"category":"A3","nav_altitude_mcp":35008,"nav_heading":90.00,"lat":38.418445,"lon":-84.269043,"nic":8,"rc":186,"seen_pos":22.186,"r_dst":176.627,"r_dir":160.9,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":763,"seen":4.6,"rssi":-29.1},
{"hex":"ad3528","type":"adsb_icao","flight":"FDX1615 ","alt_baro":26000,"alt_geom":27100,"gs":440.5,"track":267.40,"baro_rate":0,"squawk":"1643","emergency":"none","category":"A4","nav_qnh":1013.6,"nav_altitude_mcp":26016,"nav_heading":272.81,"nav_modes":["vnav","tcas"],"lat":40.281555,"lon":-84.235046,"nic":8,"rc":186,"seen_pos":1.010,"r_dst":79.870,"r_dir":133.5,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5207,"seen":1.0,"rssi":-14.2},
{"hex":"a445a9","type":"adsb_icao","flight":"UAL1965 ","alt_baro":35000,"alt_geom":36750,"gs":511.7,"track":20.60,"baro_rate":0,"squawk":"6747","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":35008,"nav_heading":21.80,"lat":38.842954,"lon":-84.222717,"nic":8,"rc":186,"seen_pos":4.348,"r_dst":153.449,"r_dir":157.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1478,"seen":0.4,"rssi":-23.2},
{"hex":"4007f7","type":"adsb_icao","flight":"BAW120 ","alt_baro":24000,"alt_geom":25000,"gs":439.6,"track":51.28,"baro_rate":0,"squawk":"6620","emergency":"none","category":"A5","nav_qnh":1012.8,"nav_altitude_mcp":24000,"nav_heading":54.84,"lat":40.127843,"lon":-84.217508,"nic":8,"rc":186,"seen_pos":0.570,"r_dst":87.105,"r_dir":137.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2104,"seen":0.3,"rssi":-12.0},
{"hex":"a654cb","type":"adsb_icao","flight":"JBU1075 ","alt_baro":39000,"alt_geom":41050,"gs":488.3,"track":11.82,"baro_rate":0,"category":"A3","lat":37.435261,"lon":-84.202324,"nic":8,"rc":186,"seen_pos":7.878,"r_dst":234.166,"r_dir":164.7,"version":2,"nac_v":2,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":70,"seen":1.4,"rssi":-30.2},
{"hex":"c02eca","type":"adsb_icao","flight":"ACA62 ","alt_baro":37700,"alt_geom":38950,"gs":530.3,"track":111.92,"geom_rate":-832,"squawk":"4114","emergency":"none","category":"A5","nav_qnh":1012.8,"nav_altitude_mcp":31008,"nav_heading":125.16,"nav_modes":["autopilot","vnav","lnav","tcas"],"lat":43.460471,"lon":-84.172015,"nic":8,"rc":186,"seen_pos":5.345,"r_dst":147.738,"r_dir":23.1,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2437,"seen":3.5,"rssi":-29.6},
{"hex":"a8a6d0","type":"adsb_icao","flight":"AAL3111 ","alt_baro":26275,"alt_geom":27425,"gs":498.3,"track":94.14,"baro_rate":-896,"squawk":"1546","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":24992,"nav_heading":102.66,"lat":40.024750,"lon":-84.166321,"nic":8,"rc":186,"seen_pos":2.665,"r_dst":93.322,"r_dir":138.9,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13290,"seen":0.6,"rssi":-17.5},
{"hex":"a2b513","type":"adsb_icao","flight":"N2738B ","alt_baro":3900,"alt_geom":4000,"gs":183.0,"track":245.12,"geom_rate":64,"squawk":"7460","emergency":"none","category":"A1","lat":40.756019,"lon":-84.133176,"nic":8,"rc":186,"seen_pos":0.496,"r_dst":67.543,"r_dir":113.0,"version":2,"nic_baro":0,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1323,"seen":0.5,"rssi":-27.0},
{"hex":"ac6573","type":"adsb_icao","flight":"SWA2190 ","alt_baro":37875,"alt_geom":39575,"gs":481.7,"track":19.15,"baro_rate":128,"squawk":"6705","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":38016,"nav_heading":279.84,"lat":40.166794,"lon":-84.131409,"nic":8,"rc":186,"seen_pos":0.060,"r_dst":88.100,"r_dir":134.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":16673,"seen":0.1,"rssi":-13.0},
{"hex":"a50192","type":"adsb_icao","flight":"RPA4475 ","alt_baro":30000,"alt_geom":31250,"gs":330.0,"track":180.69,"baro_rate":64,"squawk":"3376","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":30016,"nav_modes":["autopilot","althold","tcas"],"lat":40.534151,"lon":-84.118132,"nic":8,"rc":186,"seen_pos":0.512,"r_dst":74.528,"r_dir":122.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":11450,"seen":0.5,"rssi":-12.9},
{"hex":"485342","type":"adsb_icao","flight":"KLM166 ","alt_baro":37000,"alt_geom":38775,"gs":564.0,"track":77.20,"geom_rate":-64,"squawk":"2440","emergency":"none","category":"A5","nav_qnh":1012.8,"nav_altitude_mcp":36992,"nav_heading":70.31,"nav_modes":["autopilot","vnav","tcas"],"lat":39.913516,"lon":-84.074768,"nic":8,"rc":186,"seen_pos":0.551,"r_dst":101.149,"r_dir":139.5,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8917,"seen":0.6,"rssi":-13.7},
{"hex":"a56c19","type":"adsb_icao","flight":"AAL513 ","alt_baro":33050,"alt_geom":34425,"gs":506.2,"track":100.36,"baro_rate":0,"squawk":"1050","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":32992,"lat":41.428280,"lon":-84.072173,"nic":8,"rc":186,"seen_pos":0.583,"r_dst":65.778,"r_dir":77.7,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":3,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":17952,"seen":0.4,"rssi":-11.3},
{"hex":"a8f235","type":"adsb_icao","flight":"EDV4894 ","alt_baro":32000,"alt_geom":33400,"gs":410.1,"track":198.48,"geom_rate":64,"squawk":"4134","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":32000,"nav_heading":208.12,"lat":40.418003,"lon":-83.986047,"nic":8,"rc":186,"seen_pos":0.358,"r_dst":83.432,"r_dir":124.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5175,"seen":0.3,"rssi":-20.9},
{"hex":"a99a3d","type":"adsb_icao","flight":"SKW176R ","alt_baro":23500,"alt_geom":24350,"gs":405.2,"track":322.62,"geom_rate":1472,"squawk":"7437","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":28000,"lat":42.947296,"lon":-83.962502,"nic":8,"rc":186,"seen_pos":1.729,"r_dst":125.094,"r_dir":32.7,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":801,"seen":1.0,"rssi":-26.3},
{"hex":"a2c35b","type":"adsb_icao","flight":"UAL1369 ","alt_baro":39000,"alt_geom":40550,"gs":490.1,"track":123.01,"baro_rate":0,"squawk":"3262","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":39008,"nav_heading":139.22,"lat":41.505432,"lon":-83.957457,"nic":8,"rc":186,"seen_pos":1.117,"r_dst":71.834,"r_dir":74.9,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":14765,"seen":0.5,"rssi":-19.1},
{"hex":"a967eb","type":"adsb_icao","flight":"JBU798 ","alt_baro":35975,"alt_geom":37800,"gs":491.6,"track":60.78,"baro_rate":0,"squawk":"0746","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":109.69,"lat":38.701538,"lon":-83.927427,"nic":8,"rc":186,"seen_pos":1.821,"r_dst":166.781,"r_dir":153.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1868,"seen":0.3,"rssi":-19.3},
{"hex":"aa883e","type":"adsb_icao","flight":"ENY3500 ","alt_baro":17350,"alt_geom":18000,"gs":362.8,"track":279.36,"baro_rate":2112,"squawk":"7410","emergency":"none","category":"A3","nav_qnh":1016.0,"nav_altitude_mcp":23008,"nav_modes":["autopilot","vnav","tcas"],"lat":42.381180,"lon":-83.908830,"nic":8,"rc":186,"seen_pos":1.205,"r_dst":100.336,"r_dir":44.7,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":877,"seen":0.6,"rssi":-23.4},
{"hex":"a67452","type":"adsb_icao","flight":"DAL424 ","alt_baro":32000,"alt_geom":33175,"gs":421.0,"track":269.73,"baro_rate":-64,"squawk":"3046","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32000,"nav_heading":0.00,"lat":42.689026,"lon":-83.888917,"nic":8,"rc":186,"seen_pos":2.051,"r_dst":114.561,"r_dir":38.4,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5766,"seen":0.2,"rssi":-20.6},
{"hex":"a5adf1","type":"adsb_icao","flight":"UAL313 ","alt_baro":34975,"alt_geom":36500,"gs":502.2,"track":28.81,"baro_rate":0,"squawk":"7474","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":35008,"lat":40.528704,"lon":-83.870316,"nic":8,"rc":186,"seen_pos":0.267,"r_dst":84.379,"r_dir":118.2,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":12149,"seen":0.1,"rssi":-15.1},
{"hex":"789230","type":"adsb_icao","flight":"CPA826 ","alt_baro":35500,"alt_geom":36675,"gs":501.4,"track":112.27,"baro_rate":-960,"squawk":"1450","category":"A5","nav_qnh":1013.6,"nav_altitude_mcp":31008,"nav_heading":125.86,"lat":43.366203,"lon":-83.853346,"nic":8,"rc":186,"seen_pos":11.414,"r_dst":148.998,"r_dir":28.8,"version":2,"nic_baro":1,"nac_p":9,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":4199,"seen":0.2,"rssi":-27.3},
{"hex":"a12b2f","type":"adsb_icao","flight":"UAL413 ","alt_baro":32000,"alt_geom":33600,"gs":425.9,"track":266.23,"baro_rate":0,"squawk":"5645","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32000,"nav_heading":272.11,"lat":38.674255,"lon":-83.824370,"nic":8,"rc":186,"seen_pos":0.387,"r_dst":170.359,"r_dir":152.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2014,"seen":0.2,"rssi":-26.7},
{"hex":"aaa593","type":"adsb_icao","flight":"UAL2234 ","alt_baro":37000,"alt_geom":38675,"gs":530.4,"track":52.89,"baro_rate":0,"squawk":"4004","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":57.66,"lat":40.209366,"lon":-83.808350,"nic":8,"rc":186,"seen_pos":0.135,"r_dst":97.443,"r_dir":127.2,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":8735,"seen":0.1,"rssi":-14.0},
{"hex":"0d0eb6","type":"adsb_icao","flight":"AMX406 ","alt_baro":39000,"alt_geom":40875,"gs":503.2,"track":12.63,"baro_rate":0,"squawk":"1727","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":39008,"nav_heading":14.77,"lat":39.144470,"lon":-83.770633,"nic":8,"rc":186,"seen_pos":0.477,"r_dst":146.924,"r_dir":146.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":3103,"seen":0.4,"rssi":-18.6},
{"hex":"a84ab1","type":"adsb_icao","flight":"UPS224 ","alt_baro":31025,"alt_geom":32375,"gs":453.9,"track":197.44,"baro_rate":-640,"squawk":"0762","emergency":"none","category":"A5","nav_qnh":1013.6,"nav_altitude_mcp":31008,"nav_heading":210.94,"lat":41.009449,"lon":-83.748654,"nic":8,"rc":186,"seen_pos":0.434,"r_dst":80.087,"r_dir":97.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":9955,"seen":0.2,"rssi":-10.6},
{"hex":"a53ba4","type":"adsb_icao","flight":"RPA4673 ","alt_baro":27000,"alt_geom":28200,"gs":475.8,"track":116.46,"baro_rate":0,"squawk":"3125","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":27008,"nav_modes":["autopilot","vnav","tcas"],"lat":38.873840,"lon":-83.713910,"nic":8,"rc":186,"seen_pos":1.386,"r_dst":162.222,"r_dir":149.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13677,"seen":0.1,"rssi":-28.5},
{"hex":"a41f37","type":"adsb_icao","flight":"DAL1123 ","alt_baro":25975,"alt_geom":27025,"gs":441.2,"track":181.82,"baro_rate":448,"squawk":"4120","emergency":"none","category":"A3","nav_altitude_mcp":28000,"nav_heading":0.00,"lat":40.907410,"lon":-83.702515,"nic":8,"rc":186,"seen_pos":1.759,"r_dst":83.307,"r_dir":101.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":4934,"seen":0.1,"rssi":-10.0},
{"hex":"a07bef","type":"adsb_icao","flight":"RPA4717 ","alt_baro":28000,"alt_geom":29200,"gs":439.2,"track":296.21,"baro_rate":64,"squawk":"6736","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":28000,"nav_modes":["autopilot","vnav","tcas"],"lat":40.128448,"lon":-83.619253,"nic":8,"rc":186,"seen_pos":0.512,"r_dst":107.273,"r_dir":126.4,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1337,"seen":0.5,"rssi":-19.0},
{"hex":"a6045d","type":"adsb_icao","flight":"UAL2426 ","alt_baro":9700,"alt_geom":9975,"gs":309.4,"track":174.62,"baro_rate":-512,"category":"A3","nav_qnh":1016.0,"nav_altitude_mcp":8000,"nav_heading":189.84,"lat":42.373764,"lon":-83.547530,"nic":8,"rc":186,"seen_pos":0.674,"r_dst":112.120,"r_dir":50.6,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":3503,"seen":0.7,"rssi":-27.8},
{"hex":"a9eeb6","type":"adsb_icao","flight":"RPA3714 ","alt_baro":29000,"alt_geom":30250,"gs":492.4,"track":48.54,"baro_rate":0,"squawk":"7363","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":29024,"nav_modes":["autopilot","althold","tcas"],"lat":40.393661,"lon":-83.488831,"nic":8,"rc":186,"seen_pos":0.902,"r_dst":103.554,"r_dir":117.4,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5814,"seen":0.9,"rssi":-20.3},
{"hex":"ac36a9","type":"adsb_icao","flight":"SWA737 ","alt_baro":36000,"alt_geom":37625,"gs":391.0,"track":232.69,"baro_rate":0,"squawk":"7377","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":241.17,"lat":40.612219,"lon":-83.460950,"nic":8,"rc":186,"seen_pos":0.403,"r_dst":99.114,"r_dir":110.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":11861,"seen":0.1,"rssi":-14.8},
{"hex":"ad2baa","type":"adsb_icao","flight":"JBU161 ","alt_baro":32025,"alt_geom":33125,"gs":417.0,"track":276.89,"baro_rate":0,"squawk":"3040","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32000,"lat":42.920105,"lon":-83.416443,"nic":8,"rc":186,"seen_pos":1.165,"r_dst":138.709,"r_dir":41.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":3542,"seen":1.2,"rssi":-28.7},
{"hex":"acae2e","type":"adsb_icao","flight":"AAL1722 ","alt_baro":36000,"alt_geom":37775,"gs":466.0,"track":0.49,"baro_rate":0,"squawk":"3321","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":0.00,"lat":39.089264,"lon":-83.299056,"nic":8,"rc":186,"seen_pos":0.220,"r_dst":162.255,"r_dir":140.8,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2638,"seen":0.1,"rssi":-22.1},
{"hex":"acd120","type":"adsb_icao","flight":"DAL2396 ","alt_baro":34000,"alt_geom":35675,"gs":497.0,"track":47.77,"baro_rate":0,"squawk":"1433","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":52.73,"lat":39.122040,"lon":-83.298519,"nic":8,"rc":186,"seen_pos":2.361,"r_dst":160.719,"r_dir":140.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2745,"seen":0.5,"rssi":-22.2},
{"hex":"aaae4b","type":"adsb_icao","flight":"AAL240 ","alt_baro":35000,"alt_geom":36725,"gs":541.7,"track":45.30,"baro_rate":0,"squawk":"6731","emergency":"none","category":"A5","nav_qnh":1012.8,"nav_altitude_mcp":35008,"nav_heading":49.92,"lat":39.169052,"lon":-83.281144,"nic":8,"rc":186,"seen_pos":4.447,"r_dst":159.021,"r_dir":139.5,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5196,"seen":1.8,"rssi":-22.8},
{"hex":"aa21ef","type":"adsb_icao","flight":"SKW6433 ","alt_baro":30000,"alt_geom":31075,"gs":408.7,"track":266.63,"geom_rate":0,"squawk":"3442","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":30016,"lat":42.991974,"lon":-83.211343,"nic":8,"rc":186,"seen_pos":4.382,"r_dst":148.047,"r_dir":42.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":3222,"seen":0.3,"rssi":-28.0},
{"hex":"aca02f","type":"adsb_icao","flight":"EDV5200 ","alt_baro":34000,"alt_geom":35625,"gs":518.8,"track":58.64,"geom_rate":0,"squawk":"6741","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":34016,"lat":40.438431,"lon":-83.120483,"nic":8,"rc":186,"seen_pos":0.340,"r_dst":117.483,"r_dir":112.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":6745,"seen":0.3,"rssi":-20.4},
{"hex":"abac48","type":"adsb_icao","flight":"AAL2808 ","alt_baro":36000,"alt_geom":37800,"gs":348.6,"track":233.98,"baro_rate":0,"squawk":"6004","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":61.17,"lat":38.994415,"lon":-83.041952,"nic":8,"rc":186,"seen_pos":1.393,"r_dst":174.201,"r_dir":138.8,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5424,"seen":0.1,"rssi":-25.0},
{"hex":"a04e60","type":"adsb_icao","flight":"RPA4461 ","alt_baro":30000,"alt_geom":31300,"gs":357.1,"track":241.57,"baro_rate":64,"squawk":"1555","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":30016,"nav_modes":["autopilot","vnav","tcas"],"lat":40.485077,"lon":-83.017822,"nic":8,"rc":186,"seen_pos":0.477,"r_dst":120.723,"r_dir":110.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5895,"seen":0.5,"rssi":-27.1},
{"hex":"a38439","type":"adsb_icao","flight":"JBU1632 ","alt_baro":34000,"alt_geom":35500,"gs":528.9,"track":49.14,"geom_rate":0,"squawk":"7411","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":34016,"nav_heading":55.55,"lat":40.717392,"lon":-83.017090,"nic":8,"rc":186,"seen_pos":0.755,"r_dst":116.306,"r_dir":103.7,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13637,"seen":0.2,"rssi":-11.7},
{"hex":"abd41a","type":"adsb_icao","flight":"RPA4423 ","alt_baro":33000,"alt_geom":34500,"gs":384.6,"track":127.29,"baro_rate":-64,"squawk":"3576","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":33024,"nav_modes":["autopilot","vnav","tcas"],"lat":40.602905,"lon":-82.973022,"nic":8,"rc":186,"seen_pos":0.652,"r_dst":120.219,"r_dir":106.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":14736,"seen":0.2,"rssi":-17.2},
{"hex":"ab197e","type":"adsb_icao","flight":"PRY04 ","alt_baro":24025,"alt_geom":25075,"gs":210.4,"track":228.66,"baro_rate":0,"category":"A1","nav_qnh":1013.6,"nav_altitude_mcp":24000,"nav_heading":246.80,"lat":40.048797,"lon":-82.961447,"nic":8,"rc":186,"seen_pos":2.375,"r_dst":134.882,"r_dir":120.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":1364,"seen":0.8,"rssi":-26.5},
{"hex":"ad3293","type":"adsb_icao","flight":"PWA95 ","alt_baro":32000,"alt_geom":33350,"gs":427.0,"track":287.59,"geom_rate":0,"squawk":"1741","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":32000,"nav_heading":293.91,"lat":41.768188,"lon":-82.960136,"nic":8,"rc":186,"seen_pos":2.918,"r_dst":119.149,"r_dir":72.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":6884,"seen":0.2,"rssi":-17.9},
{"hex":"a4491f","type":"adsb_icao","flight":"DAL2106 ","alt_baro":37000,"alt_geom":38850,"gs":436.3,"track":149.26,"baro_rate":0,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":37.97,"lat":39.208603,"lon":-82.863843,"nic":8,"rc":186,"seen_pos":3.343,"r_dst":170.196,"r_dir":133.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":4508,"seen":3.3,"rssi":-29.6},
{"hex":"a45ba2","type":"adsb_icao","flight":"DAL434 ","alt_baro":35025,"alt_geom":36750,"gs":477.8,"track":120.43,"baro_rate":0,"squawk":"6041","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":35008,"lat":39.303170,"lon":-82.862854,"nic":8,"rc":186,"seen_pos":1.267,"r_dst":166.220,"r_dir":132.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":3541,"seen":0.8,"rssi":-27.0},
{"hex":"aa9e6d","type":"adsb_icao","flight":"SWA816 ","alt_baro":33250,"alt_geom":34975,"gs":392.5,"track":225.31,"baro_rate":2432,"squawk":"6733","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":232.73,"lat":38.690781,"lon":-82.842825,"nic":8,"rc":186,"seen_pos":0.413,"r_dst":194.244,"r_dir":140.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":375,"seen":0.3,"rssi":-26.1},
{"hex":"ad9003","type":"adsb_icao","flight":"AAL564 ","alt_baro":27000,"alt_geom":28025,"gs":495.7,"track":83.98,"baro_rate":0,"squawk":"3113","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":27008,"nav_heading":90.00,"lat":42.276215,"lon":-82.836040,"nic":8,"rc":186,"seen_pos":8.186,"r_dst":135.597,"r_dir":60.8,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13311,"seen":4.0,"rssi":-30.0},
{"hex":"a0b801","type":"adsb_icao","flight":"UAL407 ","alt_baro":33975,"alt_geom":35450,"gs":401.1,"track":274.15,"baro_rate":0,"squawk":"1665","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":0.00,"lat":41.395274,"lon":-82.809193,"nic":8,"rc":186,"seen_pos":0.301,"r_dst":121.913,"r_dir":83.7,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":9828,"seen":0.2,"rssi":-10.0},
{"hex":"a445a4","type":"adsb_icao","alt_baro":32000,"alt_geom":33625,"gs":423.6,"track":273.11,"baro_rate":0,"squawk":"0545","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32000,"nav_heading":277.03,"lat":38.788488,"lon":-82.671509,"nic":8,"rc":186,"seen_pos":0.643,"r_dst":194.837,"r_dir":137.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":147,"seen":0.6,"rssi":-26.7},
{"hex":"a01b56","type":"adsb_icao","flight":"DAL468 ","alt_baro":32025,"alt_geom":33300,"gs":454.8,"track":142.32,"baro_rate":64,"squawk":"2570","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":32000,"lat":41.837520,"lon":-82.660366,"nic":8,"rc":186,"seen_pos":2.422,"r_dst":133.194,"r_dir":72.5,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":15687,"seen":0.1,"rssi":-20.6},
{"hex":"a92ae9","type":"adsb_icao","flight":"EDV5169 ","alt_baro":17150,"alt_geom":17725,"gs":395.0,"track":108.30,"geom_rate":2048,"category":"A2","nav_qnh":1016.0,"nav_altitude_mcp":23008,"lat":42.165573,"lon":-82.543092,"nic":8,"rc":186,"seen_pos":3.749,"r_dst":144.605,"r_dir":65.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":125,"seen":0.5,"rssi":-27.4},
{"hex":"a03f84","type":"adsb_icao","flight":"RPA4524 ","alt_baro":31000,"alt_geom":32175,"gs":304.1,"track":229.13,"baro_rate":64,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":31008,"nav_modes":["autopilot","althold","tcas"],"lat":42.251183,"lon":-82.537281,"nic":8,"rc":186,"seen_pos":0.783,"r_dst":146.888,"r_dir":63.7,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":24894,"seen":0.8,"rssi":-25.5},
{"hex":"a94b6b","type":"adsb_icao","flight":"UAL1501 ","alt_baro":34000,"alt_geom":35500,"gs":542.0,"track":56.90,"baro_rate":-448,"squawk":"7163","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32992,"nav_heading":61.88,"lat":41.127686,"lon":-82.493835,"nic":8,"rc":186,"seen_pos":1.195,"r_dst":135.945,"r_dir":90.9,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":11620,"seen":0.2,"rssi":-16.2},
{"hex":"ae0817","type":"adsb_icao","flight":"FAMUS17 ","alt_baro":37000,"alt_geom":38875,"gs":530.9,"track":76.38,"geom_rate":-64,"squawk":"5640","emergency":"none","category":"A5","nav_qnh":1012.8,"nav_altitude_mcp":36992,"nav_heading":68.91,"nav_modes":["althold","tcas"],"lat":39.706604,"lon":-82.443556,"nic":10,"rc":25,"seen_pos":10.061,"r_dst":166.054,"r_dir":121.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":11624,"seen":6.5,"rssi":-28.9},
{"hex":"c06171","type":"adsb_icao","flight":"POE662 ","alt_baro":31000,"alt_geom":32075,"gs":506.9,"track":86.61,"baro_rate":-64,"squawk":"5731","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":31008,"nav_modes":["autopilot","vnav","tcas"],"lat":42.700459,"lon":-82.430739,"nic":8,"rc":186,"seen_pos":4.025,"r_dst":163.851,"r_dir":55.7,"version":2,"nic_baro":1,"nac_p":9,"nac_v":4,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":19051,"seen":0.6,"rssi":-24.4},
{"hex":"adc9b6","type":"adsb_icao","flight":"JBU324 ","alt_baro":33000,"alt_geom":34275,"gs":529.6,"track":79.67,"baro_rate":0,"squawk":"3150","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":32992,"nav_heading":0.00,"lat":42.287168,"lon":-82.406531,"nic":8,"rc":186,"seen_pos":3.350,"r_dst":153.065,"r_dir":63.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":22191,"seen":2.0,"rssi":-29.5},
{"hex":"a33243","type":"adsb_icao","flight":"DAL2503 ","alt_baro":24000,"alt_geom":24900,"gs":418.3,"track":278.25,"geom_rate":0,"squawk":"7114","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":24000,"nav_heading":285.47,"lat":42.636475,"lon":-82.295900,"nic":8,"rc":186,"seen_pos":2.347,"r_dst":166.969,"r_dir":57.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":270,"seen":1.0,"rssi":-24.9},
{"hex":"a33ca9","type":"adsb_icao","flight":"JBU1096 ","alt_baro":35000,"alt_geom":36500,"gs":524.7,"track":60.80,"geom_rate":0,"squawk":"1107","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":35008,"nav_heading":68.20,"lat":40.939133,"lon":-82.236755,"nic":8,"rc":186,"seen_pos":0.346,"r_dst":148.551,"r_dir":95.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13855,"seen":0.2,"rssi":-21.3},
{"hex":"a3a0cf","type":"adsb_icao","flight":"UAL371 ","alt_baro":37000,"alt_geom":38450,"gs":338.7,"track":251.40,"baro_rate":0,"squawk":"3503","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":102.66,"lat":42.178190,"lon":-82.221041,"nic":8,"rc":186,"seen_pos":3.733,"r_dst":158.202,"r_dir":67.2,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":14132,"seen":3.1,"rssi":-28.3},
{"hex":"a4359b","type":"adsb_icao","flight":"UPS1441 ","alt_baro":26025,"alt_geom":27125,"gs":450.1,"track":204.97,"baro_rate":-128,"squawk":"7457","emergency":"none","category":"A5","nav_qnh":1012.8,"nav_altitude_mcp":26016,"nav_heading":215.16,"lat":40.825653,"lon":-82.177307,"nic":8,"rc":186,"seen_pos":0.448,"r_dst":152.222,"r_dir":97.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":835,"seen":0.2,"rssi":-19.9},
{"hex":"ac7a52","type":"adsb_icao","flight":"AAL2443 ","alt_baro":29000,"alt_geom":30200,"gs":484.5,"track":109.41,"baro_rate":0,"squawk":"6251","emergency":"none","category":"A3","nav_altitude_mcp":28992,"nav_heading":0.00,"lat":41.367901,"lon":-82.164189,"nic":8,"rc":186,"seen_pos":0.253,"r_dst":150.809,"r_dir":85.2,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":17418,"seen":0.3,"rssi":-21.3},
{"hex":"a32367","type":"adsb_icao","flight":"DAL816 ","alt_baro":37000,"alt_geom":38625,"gs":516.7,"track":61.95,"geom_rate":0,"squawk":"7223","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":36992,"nav_heading":69.61,"lat":41.307244,"lon":-82.140780,"nic":8,"rc":186,"seen_pos":2.018,"r_dst":151.740,"r_dir":86.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":18643,"seen":0.2,"rssi":-20.3},
{"hex":"a12ac0","type":"adsb_icao","flight":"UAL545 ","alt_baro":35000,"alt_geom":36525,"gs":539.9,"track":65.95,"baro_rate":0,"squawk":"7433","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":35008,"nav_heading":68.91,"lat":41.310608,"lon":-82.127502,"nic":8,"rc":186,"seen_pos":0.926,"r_dst":152.344,"r_dir":86.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":16238,"seen":0.1,"rssi":-23.8},
{"hex":"abf807","type":"adsb_icao","flight":"HBAL790 ","gs":5.0,"track":143.13,"geom_rate":64,"category":"B2","lat":38.207889,"lon":-82.124295,"nic":8,"rc":186,"seen_pos":9.280,"r_dst":238.023,"r_dir":138.0,"version":0,"nac_p":8,"nac_v":1,"sil":2,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":293,"seen":1.7,"rssi":-30.7},
{"hex":"ab0a95","type":"adsb_icao","flight":"AAL2086 ","alt_baro":36000,"alt_geom":37500,"gs":502.9,"track":83.84,"baro_rate":64,"squawk":"1306","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":250.31,"lat":41.894623,"lon":-82.114750,"nic":8,"rc":186,"seen_pos":2.233,"r_dst":157.645,"r_dir":73.6,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":20500,"seen":0.4,"rssi":-22.1},
{"hex":"aaf828","type":"adsb_icao","flight":"AAL130 ","alt_baro":38975,"alt_geom":40475,"gs":585.4,"track":63.30,"geom_rate":0,"squawk":"3465","emergency":"none","category":"A5","nav_qnh":1012.8,"nav_altitude_mcp":39008,"nav_heading":66.09,"nav_modes":["autopilot","vnav","lnav","tcas"],"lat":42.687515,"lon":-82.112565,"nic":8,"rc":186,"seen_pos":0.079,"r_dst":175.524,"r_dir":58.4,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":20306,"seen":0.1,"rssi":-27.1},
{"hex":"ac5700","type":"adsb_icao","flight":"SWA427 ","alt_baro":38000,"alt_geom":39575,"gs":377.2,"track":262.54,"baro_rate":0,"squawk":"7334","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":38016,"nav_heading":270.70,"lat":41.758438,"lon":-82.104825,"nic":8,"rc":186,"seen_pos":4.348,"r_dst":156.289,"r_dir":76.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":4359,"seen":0.2,"rssi":-19.1},
{"hex":"a37335","type":"adsb_icao","flight":"DAL2646 ","alt_baro":37000,"alt_geom":38450,"gs":541.4,"track":84.59,"baro_rate":0,"squawk":"2551","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":0.00,"lat":42.466162,"lon":-82.050242,"nic":8,"rc":186,"seen_pos":0.112,"r_dst":171.896,"r_dir":62.7,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":19906,"seen":0.1,"rssi":-26.3},
{"hex":"a8b44e","type":"adsb_icao","alt_baro":37000,"alt_geom":38800,"gs":488.1,"track":12.78,"baro_rate":0,"squawk":"7126","emergency":"none","nav_qnh":1012.8,"nav_altitude_mcp":36992,"lat":39.715064,"lon":-82.027527,"nic":8,"rc":186,"seen_pos":3.207,"r_dst":182.067,"r_dir":118.3,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":242,"seen":0.9,"rssi":-28.1},
{"hex":"a7d7b5","type":"adsb_icao","flight":"SKW6398 ","alt_baro":34000,"alt_geom":35175,"gs":392.5,"track":267.23,"geom_rate":0,"squawk":"3516","emergency":"none","category":"A2","nav_qnh":1012.8,"nav_altitude_mcp":34016,"lat":43.040197,"lon":-82.008820,"nic":8,"rc":186,"seen_pos":0.621,"r_dst":190.556,"r_dir":53.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":669,"seen":0.2,"rssi":-23.9},
{"hex":"a1e58f","type":"adsb_icao","flight":"RPA5779 ","alt_baro":30000,"alt_geom":31200,"gs":414.6,"track":99.44,"baro_rate":-64,"squawk":"5616","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":30016,"nav_modes":["autopilot","althold","tcas"],"lat":41.938725,"lon":-82.001634,"nic":8,"rc":186,"seen_pos":5.810,"r_dst":163.197,"r_dir":73.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":19806,"seen":0.3,"rssi":-22.7},
{"hex":"a0a903","type":"adsb_icao","flight":"UAL1558 ","alt_baro":32000,"alt_geom":33150,"gs":409.7,"track":278.70,"baro_rate":0,"emergency":"none","category":"A4","nav_qnh":1013.6,"nav_altitude_mcp":32000,"nav_heading":291.80,"lat":42.733325,"lon":-81.849933,"nic":8,"rc":186,"seen_pos":0.474,"r_dst":187.001,"r_dir":59.4,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":268,"seen":0.5,"rssi":-21.8},
{"hex":"a07b8f","type":"adsb_icao","flight":"DAL2594 ","alt_baro":38000,"alt_geom":39675,"gs":396.3,"track":291.77,"geom_rate":0,"squawk":"7144","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":38016,"nav_heading":292.50,"lat":41.399976,"lon":-81.781716,"nic":8,"rc":186,"seen_pos":2.183,"r_dst":168.110,"r_dir":84.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":5160,"seen":0.3,"rssi":-20.3},
{"hex":"c00b93","type":"adsb_icao","flight":"JZA755 ","alt_baro":24000,"alt_geom":24950,"gs":356.8,"track":212.18,"baro_rate":0,"squawk":"6353","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":24000,"nav_modes":["autopilot","vnav","tcas"],"lat":41.678879,"lon":-81.770991,"nic":8,"rc":186,"seen_pos":14.785,"r_dst":170.224,"r_dir":79.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":312,"seen":2.0,"rssi":-28.0},
{"hex":"aaa2d3","type":"adsb_icao","flight":"SWA2941 ","alt_baro":39000,"alt_geom":40700,"gs":478.7,"track":134.75,"baro_rate":0,"squawk":"1334","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":39008,"nav_heading":125.86,"lat":40.751084,"lon":-81.752763,"nic":8,"rc":186,"seen_pos":1.390,"r_dst":172.006,"r_dir":97.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":18592,"seen":0.2,"rssi":-19.9},
{"hex":"a4ddca","type":"adsb_icao","flight":"RPA4456 ","alt_baro":29000,"alt_geom":30175,"gs":530.5,"track":64.55,"baro_rate":0,"squawk":"6763","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":29024,"nav_modes":["autopilot","althold","tcas"],"lat":41.300079,"lon":-81.705750,"nic":8,"rc":186,"seen_pos":1.772,"r_dst":171.352,"r_dir":86.8,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":14012,"seen":0.1,"rssi":-19.7},
{"hex":"a7e20a","type":"adsb_icao","flight":"JBU1172 ","alt_baro":34000,"alt_geom":35425,"gs":462.0,"track":15.57,"baro_rate":64,"squawk":"7307","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":34016,"nav_heading":0.00,"lat":42.027267,"lon":-81.606765,"nic":8,"rc":186,"seen_pos":3.174,"r_dst":181.590,"r_dir":72.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":15934,"seen":0.3,"rssi":-24.0},
{"hex":"a413f8","type":"adsb_icao","flight":"ABX480 ","alt_baro":37000,"alt_geom":38550,"gs":544.9,"track":71.27,"baro_rate":0,"squawk":"6703","emergency":"none","category":"A5","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":80.16,"lat":41.591675,"lon":-81.576420,"nic":8,"rc":186,"seen_pos":0.680,"r_dst":178.220,"r_dir":81.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":3,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":15579,"seen":0.2,"rssi":-22.1},
{"hex":"a34ae6","type":"adsb_icao","flight":"DAL1297 ","alt_baro":31000,"alt_geom":32300,"gs":519.5,"track":67.24,"geom_rate":0,"squawk":"4175","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":31008,"nav_heading":77.34,"lat":41.529739,"lon":-81.464247,"nic":8,"rc":186,"seen_pos":0.378,"r_dst":182.880,"r_dir":82.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":18166,"seen":0.2,"rssi":-22.4},
{"hex":"abc3ec","type":"adsb_icao","flight":"SWA1157 ","alt_baro":37000,"alt_geom":38725,"gs":481.1,"track":133.15,"baro_rate":0,"squawk":"1312","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":149.06,"lat":40.473953,"lon":-81.431824,"nic":8,"rc":186,"seen_pos":0.111,"r_dst":189.899,"r_dir":102.0,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":20902,"seen":0.1,"rssi":-27.3},
{"hex":"a00d5b","type":"adsb_icao","flight":"AAL1805 ","alt_baro":37000,"alt_geom":38375,"gs":545.9,"track":85.27,"baro_rate":0,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":0.00,"lat":42.536087,"lon":-81.415058,"nic":8,"rc":186,"seen_pos":3.070,"r_dst":199.342,"r_dir":65.0,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":24759,"seen":1.5,"rssi":-29.8},
{"hex":"a66695","type":"adsb_icao","flight":"EJA511 ","alt_baro":43000,"alt_geom":44425,"gs":348.6,"track":273.29,"baro_rate":-64,"category":"A2","lat":43.041268,"lon":-81.226894,"nic":9,"rc":75,"seen_pos":7.310,"r_dst":219.907,"r_dir":58.5,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":209,"seen":0.5,"rssi":-28.7},
{"hex":"a23f0c","type":"adsb_icao","flight":"MXY1407 ","alt_baro":29750,"alt_geom":31000,"gs":519.2,"track":86.02,"geom_rate":768,"squawk":"6657","emergency":"none","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":35008,"nav_heading":98.44,"lat":41.391876,"lon":-81.175679,"nic":8,"rc":186,"seen_pos":3.222,"r_dst":195.364,"r_dir":85.3,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":4394,"seen":0.2,"rssi":-21.5},
{"hex":"a0ad1d","type":"adsb_icao","flight":"UAL1819 ","alt_baro":36000,"alt_geom":37575,"gs":396.5,"track":274.92,"baro_rate":0,"squawk":"7150","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_heading":278.44,"lat":41.065172,"lon":-81.163184,"nic":8,"rc":186,"seen_pos":0.804,"r_dst":196.269,"r_dir":91.0,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":531,"seen":0.8,"rssi":-24.0},
{"hex":"a838e5","type":"adsb_icao","flight":"JIA5580 ","alt_baro":31025,"alt_geom":32550,"gs":522.4,"track":71.70,"geom_rate":0,"category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":31008,"nav_heading":78.05,"lat":39.752399,"lon":-81.159424,"nic":8,"rc":186,"seen_pos":0.404,"r_dst":216.511,"r_dir":112.3,"version":0,"nic_baro":1,"nac_p":8,"nac_v":1,"sil":2,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":2000,"seen":0.1,"rssi":-27.7},
{"hex":"a5954e","type":"adsb_icao","flight":"UAL2357 ","alt_baro":37000,"alt_geom":38775,"gs":479.8,"track":127.04,"baro_rate":0,"category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":36992,"lat":40.077988,"lon":-81.148621,"nic":8,"rc":186,"seen_pos":2.797,"r_dst":209.425,"r_dir":107.4,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":22973,"seen":0.7,"rssi":-25.9},
{"hex":"ad41ed","type":"adsb_icao","flight":"AAL2875 ","alt_baro":35000,"alt_geom":36675,"gs":519.7,"track":101.09,"baro_rate":-64,"category":"A3","lat":40.671204,"lon":-81.039795,"nic":8,"rc":186,"seen_pos":8.345,"r_dst":204.787,"r_dir":97.5,"version":2,"nac_v":1,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":25404,"seen":3.6,"rssi":-28.1},
{"hex":"a23fa8","type":"adsb_icao","flight":"N244JE ","alt_baro":41000,"alt_geom":42600,"gs":562.3,"track":67.63,"baro_rate":0,"category":"A2","nav_qnh":1013.6,"nav_altitude_mcp":41024,"nav_modes":["autopilot","althold","tcas"],"lat":41.676682,"lon":-80.992233,"nic":8,"rc":186,"seen_pos":0.067,"r_dst":204.839,"r_dir":80.6,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":17962,"seen":0.1,"rssi":-21.7},
{"hex":"acf4e8","type":"adsb_icao","flight":"DAL305 ","alt_baro":35000,"alt_geom":36475,"gs":545.4,"track":67.81,"baro_rate":0,"squawk":"3342","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":35008,"nav_heading":75.94,"lat":41.715454,"lon":-80.867552,"nic":8,"rc":186,"seen_pos":26.219,"r_dst":210.660,"r_dir":80.1,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":19693,"seen":10.4,"rssi":-31.3},
{"hex":"a9ad36","type":"adsb_icao","alt_baro":28000,"alt_geom":29200,"gs":383.3,"track":277.50,"baro_rate":0,"squawk":"7134","emergency":"none","nav_qnh":1013.6,"nav_altitude_mcp":28000,"nav_modes":["autopilot","vnav","tcas"],"lat":41.237508,"lon":-80.863994,"nic":8,"rc":186,"seen_pos":3.107,"r_dst":209.350,"r_dir":87.9,"version":2,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":102,"seen":1.6,"rssi":-29.4},
{"hex":"ad9b28","type":"adsb_icao","flight":"AAL2595 ","alt_baro":37000,"alt_geom":38550,"gs":546.7,"track":67.75,"baro_rate":0,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36992,"nav_heading":75.23,"lat":41.745667,"lon":-80.767864,"nic":8,"rc":186,"seen_pos":17.391,"r_dst":215.314,"r_dir":79.8,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":13786,"seen":0.2,"rssi":-30.3},
{"hex":"a5ce0f","type":"adsb_icao","flight":"UPS1123 ","alt_baro":38025,"alt_geom":39800,"gs":402.7,"track":238.40,"baro_rate":0,"squawk":"3512","emergency":"none","category":"A4","nav_qnh":1012.8,"nav_altitude_mcp":38016,"nav_heading":248.91,"nav_modes":["vnav","tcas"],"lat":40.581345,"lon":-80.734924,"nic":8,"rc":186,"seen_pos":1.070,"r_dst":219.449,"r_dir":98.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":248,"seen":1.1,"rssi":-23.2},
{"hex":"a1226e","type":"adsb_icao","flight":"AAL2513 ","alt_baro":31000,"alt_geom":32150,"gs":495.5,"track":87.34,"baro_rate":0,"category":"A3","nav_altitude_mcp":31008,"nav_heading":0.00,"lat":41.407883,"lon":-80.505690,"nic":8,"rc":186,"seen_pos":37.711,"r_dst":225.553,"r_dir":85.2,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":18769,"seen":36.4,"rssi":-29.0},
{"hex":"aa03a2","type":"adsb_icao","flight":"RPA3588 ","alt_baro":33000,"alt_geom":34325,"gs":438.6,"track":73.71,"baro_rate":64,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":33024,"nav_modes":["autopilot","althold","tcas"],"lat":41.857773,"lon":-80.394766,"nic":8,"rc":186,"seen_pos":26.933,"r_dst":232.754,"r_dir":78.6,"version":0,"nic_baro":1,"nac_p":8,"nac_v":2,"sil":2,"sil_type":"perhour","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":12629,"seen":6.4,"rssi":-30.7},
{"hex":"a38367","type":"adsb_icao","flight":"JBU26 ","alt_baro":37000,"alt_geom":38550,"gs":539.1,"track":91.06,"geom_rate":0,"category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":36992,"nav_heading":104.06,"lat":41.762152,"lon":-80.264900,"nic":8,"rc":186,"seen_pos":0.351,"r_dst":237.803,"r_dir":80.2,"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":24144,"seen":0.4,"rssi":-27.8},
{"hex":"ac12be","type":"mode_s","flight":"SWA200 ","alt_baro":38000,"alt_geom":40000,"category":"A3","lastPosition":{"lat":37.583577,"lon":-84.334637,"nic":8,"rc":186,"seen_pos":299.989},"version":0,"sil_type":"perhour","mlat":[],"tisb":[],"messages":115,"seen":58.7,"rssi":-31.7},
{"hex":"a12a9e","type":"mode_s","alt_baro":35000,"alt_geom":36750,"gs":532.7,"track":41.65,"baro_rate":0,"nav_qnh":1013.6,"nav_altitude_mcp":35008,"nav_heading":54.14,"version":0,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"unknown","alert":0,"spi":0,"mlat":[],"tisb":[],"messages":29,"seen":11.2,"rssi":-30.1},
{"hex":"ae10bb","type":"mode_s","alt_baro":33000,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":508,"seen":3.5,"rssi":-28.3},
{"hex":"a19aaa","type":"mode_s","flight":"ENY3408 ","alt_baro":36000,"alt_geom":37750,"gs":374.3,"track":256.56,"baro_rate":-64,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":36000,"nav_modes":["autopilot","vnav","tcas"],"version":0,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":61,"seen":0.4,"rssi":-30.7},
{"hex":"a56a56","type":"mode_s","flight":"SWA1603 ","gs":541.6,"track":85.55,"baro_rate":0,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":39008,"nav_heading":96.33,"lastPosition":{"lat":42.357513,"lon":-81.338716,"nic":8,"rc":186,"seen_pos":110.019},"version":0,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"mlat":[],"tisb":[],"messages":15165,"seen":6.7,"rssi":-30.7},
{"hex":"ab84c9","type":"mode_s","mlat":[],"tisb":[],"messages":11,"seen":12.0,"rssi":-30.9},
{"hex":"ae1236","type":"mode_s","alt_baro":36975,"alert":0,"spi":0,"mlat":[],"tisb":[],"messages":12295,"seen":1.0,"rssi":-21.0},
{"hex":"ab3c0a","type":"adsb_icao","flight":"ASA287 ","category":"A3","lastPosition":{"lat":40.343307,"lon":-88.843140,"nic":8,"rc":186,"seen_pos":75.426},"version":2,"sil_type":"perhour","mlat":[],"tisb":[],"messages":39504,"seen":0.4,"rssi":-30.1},
{"hex":"a12797","type":"mode_s","flight":"UAL1731 ","gs":537.7,"track":103.44,"baro_rate":0,"category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":35008,"nav_heading":116.72,"lastPosition":{"lat":42.132202,"lon":-80.955381,"nic":8,"rc":186,"seen_pos":268.358},"version":0,"nic_baro":1,"nac_p":10,"nac_v":2,"sil":3,"sil_type":"perhour","mlat":[],"tisb":[],"messages":20974,"seen":38.0,"rssi":-29.9},
{"hex":"a3ec93","type":"adsb_icao","flight":"DAL2612 ","gs":454.3,"track":172.16,"baro_rate":0,"category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":35008,"lastPosition":{"lat":40.438532,"lon":-89.506801,"nic":8,"rc":186,"seen_pos":62.922},"version":2,"nic_baro":1,"nac_p":10,"nac_v":1,"sil":3,"sil_type":"perhour","mlat":[],"tisb":[],"messages":869,"seen":52.8,"rssi":-31.0},
{"hex":"a0a457","type":"mode_s","alt_baro":32000,"mlat":[],"tisb":[],"messages":6,"seen":5.7,"rssi":-29.7}
]
}
+362
View File
@@ -0,0 +1,362 @@
{ "actualRange": { "last24h": { "points": [
[44.2991,-85.4805,37975],
[44.1820,-85.4271,43000],
[44.5204,-85.3611,40000],
[44.3669,-85.3027,43000],
[44.3522,-85.1918,43000],
[44.4745,-85.0664,40000],
[44.4049,-85.0343,37025],
[44.5947,-84.9164,45000],
[44.5958,-84.8453,36000],
[44.6490,-84.7090,36000],
[44.5123,-84.7210,36000],
[44.5527,-84.5659,45000],
[44.4547,-84.5338,41000],
[44.5302,-84.3836,39000],
[44.4248,-84.4003,37000],
[44.4274,-84.3074,37000],
[44.4295,-84.2343,37000],
[44.2132,-84.2387,43000],
[44.1859,-84.1474,35000],
[43.8941,-84.2367,45000],
[44.2588,-83.9096,41000],
[44.2704,-83.8926,41000],
[43.8630,-84.0262,45000],
[44.4223,-83.5906,43000],
[44.2660,-83.5559,41000],
[44.2587,-83.5189,41000],
[44.2344,-83.3971,41000],
[44.2348,-83.2856,37000],
[44.2353,-83.2581,37000],
[44.1942,-83.1965,41000],
[44.3403,-82.9224,45000],
[44.0801,-83.0572,43000],
[44.1324,-82.8924,41000],
[44.1178,-82.8208,41000],
[44.0993,-82.7309,41000],
[44.0825,-82.6497,41600],
[43.9757,-82.6868,41000],
[44.0560,-82.5220,42950],
[43.6079,-82.8484,35000],
[43.9813,-82.3530,40000],
[43.9904,-82.1449,44250],
[43.6393,-82.5594,45000],
[43.9598,-81.9442,44775],
[43.7394,-82.2089,42975],
[43.5951,-82.1983,43000],
[43.7236,-81.9400,43000],
[43.7125,-81.7568,43000],
[43.7060,-81.6520,43000],
[43.6997,-81.5530,42975],
[43.6579,-81.4423,43000],
[43.5779,-81.4456,43000],
[43.5932,-81.2944,43000],
[43.5064,-81.2478,41000],
[43.5137,-81.1140,43000],
[43.3926,-81.1367,45000],
[43.2919,-81.2173,38975],
[43.2480,-81.2527,43000],
[43.1111,-81.2812,45000],
[43.1701,-81.0053,45000],
[43.0930,-80.9351,41000],
[43.1600,-80.7201,45000],
[42.9990,-80.9281,43000],
[42.9279,-80.7337,45000],
[42.7948,-81.0319,41000],
[42.6470,-81.1674,45000],
[42.7800,-80.4876,45000],
[42.7624,-80.4283,47000],
[42.6494,-80.6602,41000],
[42.6684,-80.2248,45025],
[42.4568,-80.6192,47000],
[42.4565,-80.4561,45000],
[42.3328,-80.7894,37025],
[42.3838,-80.1998,47000],
[42.2426,-80.4084,39000],
[42.1737,-80.1989,41000],
[42.1601,-80.0853,41000],
[42.0754,-80.3638,47000],
[41.9731,-80.1505,45000],
[41.9312,-80.0366,47025],
[41.8971,-80.0895,47025],
[41.7841,-79.9205,45000],
[41.7559,-80.0415,45000],
[41.6765,-80.1295,45000],
[41.5542,-79.9415,45000],
[41.5121,-80.0169,39000],
[41.4222,-79.8748,45000],
[41.3903,-80.0134,45000],
[41.2697,-80.0027,37000],
[41.2281,-79.8962,44975],
[41.1365,-79.8896,45000],
[41.0886,-79.8104,45000],
[41.0358,-80.0939,42925],
[40.9177,-80.1032,38000],
[40.8779,-80.2981,45000],
[40.7691,-80.0677,45000],
[40.7055,-80.2695,37025],
[40.6501,-80.2156,45000],
[40.5634,-80.1305,45000],
[40.4991,-80.1488,41000],
[40.4627,-80.1913,45000],
[40.3883,-80.2360,45000],
[40.3435,-80.2629,45000],
[40.2473,-80.2697,45000],
[40.1989,-80.2979,43000],
[40.1502,-80.3784,45000],
[40.0826,-80.4186,45000],
[40.0050,-80.3998,44975],
[39.9553,-80.4943,45000],
[39.8860,-80.5353,45000],
[39.8591,-80.6752,41000],
[39.7478,-80.6167,45000],
[39.7943,-81.0107,39875],
[39.5486,-80.6302,43025],
[39.5685,-80.7219,45000],
[39.5446,-80.9022,44550],
[39.6065,-81.2113,35850],
[39.6502,-81.5201,45000],
[39.5578,-81.4637,35025],
[39.4174,-81.2937,37000],
[39.3896,-81.3907,45000],
[39.4603,-81.7662,37975],
[39.3274,-81.5770,43000],
[39.2719,-81.6068,43000],
[39.2956,-81.7985,42800],
[39.1500,-81.7510,34975],
[39.0468,-81.5935,33075],
[39.2322,-82.1021,45275],
[39.0114,-81.8480,35000],
[38.9427,-81.8956,35025],
[38.8851,-81.9673,58200],
[38.6564,-81.6710,60500],
[38.7307,-81.9834,57900],
[38.7357,-82.0391,37000],
[38.3995,-81.7668,44725],
[38.5795,-82.1118,57800],
[38.3020,-81.8599,63900],
[38.2763,-81.9129,43000],
[38.2108,-82.0101,43000],
[38.1895,-82.0619,42900],
[38.1564,-82.1426,42025],
[38.1135,-82.2465,40900],
[38.1754,-82.3880,43000],
[38.0966,-82.4880,41000],
[38.1287,-82.5510,43000],
[37.9917,-82.6206,44775],
[37.8738,-82.6208,41000],
[37.8436,-82.6317,40750],
[37.9211,-82.8250,45000],
[37.8122,-82.8649,43025],
[37.7443,-82.9394,45000],
[37.7429,-82.9888,45000],
[37.9043,-83.2000,59900],
[37.8882,-83.2512,59800],
[37.8738,-83.3194,59700],
[37.7876,-83.4467,41000],
[37.7671,-83.4427,37000],
[37.8206,-83.6044,45000],
[37.5271,-83.4990,60200],
[37.7318,-83.7215,59700],
[37.6126,-83.7379,45000],
[37.7384,-83.8778,43000],
[37.7025,-84.0017,40975],
[37.4142,-83.9757,41025],
[37.2056,-84.0096,45000],
[37.6268,-84.1661,41000],
[37.3073,-84.2356,39000],
[37.1650,-84.1999,45000],
[37.4537,-84.3923,43000],
[37.1129,-84.4411,45000],
[37.0975,-84.5116,45000],
[37.0735,-84.6216,45000],
[37.0173,-84.7033,49000],
[37.0448,-84.7524,45000],
[37.2181,-84.8446,49000],
[37.2967,-85.0105,47000],
[37.4113,-85.1240,48400],
[37.3947,-85.1476,48475],
[37.4681,-85.2316,47000],
[37.8122,-85.3139,41000],
[37.8538,-85.4320,44975],
[37.8026,-85.5026,45000],
[37.8066,-85.5826,45000],
[37.5914,-85.6980,37000],
[37.6440,-85.7088,45000],
[37.6842,-85.8236,37000],
[37.6258,-85.8931,45000],
[37.3778,-86.0088,41000],
[37.3524,-86.0907,48800],
[37.4304,-86.1442,40000],
[37.7397,-86.1668,45000],
[37.6081,-86.3248,41000],
[37.6290,-86.3455,41000],
[37.6637,-86.4529,45000],
[37.7547,-86.4673,41000],
[37.8219,-86.5607,45000],
[37.8198,-86.6087,46825],
[37.7839,-86.7674,45000],
[37.9560,-86.7871,45000],
[37.7536,-86.9302,45000],
[37.8622,-86.9743,44975],
[37.8155,-87.0856,45000],
[37.6861,-87.1785,45000],
[37.7825,-87.2100,45000],
[37.8667,-87.2803,43000],
[37.7379,-87.4113,41000],
[37.6387,-87.6249,45000],
[37.8237,-87.5915,38725],
[37.7633,-87.6950,45000],
[37.8386,-87.7375,45000],
[37.7619,-87.8640,37350],
[37.7807,-87.9764,36000],
[38.1560,-87.7696,43000],
[38.0877,-87.9173,45000],
[37.9869,-88.1715,47000],
[38.1223,-88.1508,40000],
[38.1319,-88.1800,40000],
[38.1534,-88.2450,40000],
[38.2776,-88.3148,40000],
[38.2447,-88.3614,37975],
[38.3343,-88.4396,40000],
[38.3537,-88.4727,45000],
[38.3931,-88.5823,45000],
[38.3895,-88.7320,45000],
[38.3580,-88.8519,45000],
[38.3848,-88.9236,45000],
[38.5395,-88.8947,40000],
[38.4168,-89.0771,41000],
[38.6049,-89.0409,40000],
[38.6158,-89.0653,40000],
[38.8393,-88.9672,45000],
[38.8399,-88.9957,44975],
[39.1231,-88.7582,38025],
[38.9657,-89.0608,45000],
[39.0718,-89.1101,37975],
[39.0701,-89.1213,37975],
[39.3807,-88.7536,45000],
[39.2009,-89.2686,41000],
[39.1899,-89.3873,41000],
[39.2554,-89.3713,45000],
[39.2735,-89.5116,45025],
[39.5283,-89.1556,38000],
[39.5800,-89.1104,39000],
[39.4565,-89.5150,43000],
[39.8090,-89.0029,41000],
[39.6699,-89.3877,36000],
[39.8420,-89.1859,49000],
[39.8415,-89.3442,48975],
[39.8297,-89.5784,43000],
[39.7970,-89.7522,36025],
[39.9429,-89.5898,36025],
[39.9801,-89.5482,36025],
[40.1561,-89.2744,34000],
[40.1974,-89.3707,47925],
[40.1716,-89.5870,37000],
[40.1595,-89.8913,47975],
[40.2438,-89.8886,44975],
[40.2579,-89.9526,45000],
[40.2526,-90.3188,47000],
[40.4332,-89.9179,43000],
[40.4553,-90.1351,43000],
[40.4572,-90.1547,43000],
[40.6295,-89.8360,45000],
[40.6382,-89.8667,45000],
[40.7368,-89.5657,38000],
[40.8212,-89.6389,43000],
[40.8495,-89.7381,43000],
[40.9051,-89.8448,43000],
[40.9784,-89.6353,40000],
[41.0365,-89.4941,38000],
[41.0782,-89.4969,38975],
[41.1345,-90.3051,45000],
[41.1647,-90.1318,43025],
[41.2426,-90.4472,42975],
[41.3085,-90.4005,45000],
[41.3562,-90.1765,41225],
[41.3937,-90.2848,43000],
[41.4711,-90.4914,47000],
[41.5405,-90.3629,47000],
[41.6132,-90.3227,45000],
[41.6753,-90.2411,39000],
[41.7414,-90.5283,43000],
[41.7448,-89.8406,45000],
[41.8549,-90.2070,45000],
[41.8751,-90.0898,45000],
[41.9742,-90.0735,40000],
[42.0513,-90.0911,45000],
[42.0552,-90.0474,45000],
[42.0747,-89.8247,45000],
[42.1169,-89.7309,43000],
[42.2222,-89.9222,43000],
[42.3240,-89.9178,49000],
[42.3262,-89.8535,49000],
[42.3401,-89.6348,39000],
[42.4405,-89.6147,43000],
[42.5221,-89.7106,41000],
[42.5373,-89.6712,45000],
[42.6288,-89.6777,45000],
[42.6333,-89.5809,40000],
[42.6834,-89.5443,40000],
[42.7367,-89.5053,40000],
[42.6502,-89.0314,44975],
[42.8729,-89.5130,45000],
[42.9042,-89.3732,43000],
[42.9140,-89.2648,43000],
[42.7173,-88.6394,44925],
[42.9398,-88.9715,43000],
[43.1293,-89.1835,43000],
[43.1291,-89.1656,43000],
[43.0832,-88.9139,36000],
[42.9720,-88.5920,43000],
[43.0977,-88.7066,32000],
[43.1222,-88.6232,45000],
[43.1203,-88.4307,34000],
[43.1190,-88.4149,45000],
[43.1894,-88.4074,32000],
[43.1273,-88.1965,39000],
[43.1262,-88.0491,39000],
[43.2683,-88.1638,45000],
[43.2957,-88.1614,29975],
[43.4332,-88.2065,33975],
[43.5037,-88.2374,34000],
[43.5879,-88.2104,43000],
[43.5440,-88.0589,43000],
[43.4877,-87.8657,43000],
[43.5663,-87.9135,36950],
[43.6668,-87.9210,40000],
[43.5496,-87.7363,37000],
[43.6246,-87.7219,40000],
[43.6038,-87.6248,40000],
[43.6803,-87.5617,46700],
[43.6327,-87.4321,36000],
[43.9227,-87.6158,43000],
[43.5662,-87.2314,46650],
[44.0347,-87.4772,35950],
[44.0202,-87.3807,37275],
[44.2798,-87.5598,41000],
[43.9926,-87.1978,39800],
[44.2047,-87.3060,36000],
[44.3855,-87.2626,43000],
[44.1374,-87.0881,47000],
[44.2194,-87.0685,34025],
[44.3698,-87.0353,43000],
[44.3960,-86.9252,43000],
[44.3438,-86.8793,43000],
[44.3299,-86.7960,42975],
[44.5531,-86.7972,43000],
[44.5421,-86.7037,43000],
[44.3439,-86.5247,36975],
[44.6207,-86.4803,43000],
[44.6212,-86.4711,43000],
[44.5469,-86.3231,41000],
[44.6340,-86.2343,43000],
[44.7586,-86.1782,45000],
[44.6263,-86.0615,40000],
[44.6214,-86.0284,40000],
[44.6447,-85.9382,35975],
[44.0036,-85.8045,38000],
[44.1461,-85.7329,43000],
[44.1421,-85.6618,43000],
[44.1465,-85.6014,34000]
]}}}
+1
View File
@@ -0,0 +1 @@
{ "refresh": 1000, "history": 1, "lat": 41.204140, "lon": -85.499977, "jaeroTimeout": 33.0, "readsb": true, "binCraft": true, "zstd": true, "outlineJson": true, "version": "3.14.1682 wiedehopf git: 2023.02-dirty (committed: Sun Mar 12 21:20:41 2023 0100)" }
+11
View File
@@ -0,0 +1,11 @@
{ "now" : 1782178895.0, "gain_db" : 48.0, "estimated_ppm" : 0.5,
"aircraft_with_pos": 231, "aircraft_without_pos": 11, "aircraft_count_by_type": { "adsb_icao": 232, "adsb_icao_nt": 0, "adsr_icao": 0, "tisb_icao": 0, "adsc": 0, "mlat": 0, "other": 0, "mode_s": 10, "adsb_other": 0, "adsr_other": 0, "tisb_trackfile": 0, "tisb_other": 0, "mode_ac": 0, "unknown": 0},
"last1min":{"start":1782178835.0,"end":1782178895.0,"local":{"samples_processed":144048128,"samples_dropped":0,"samples_lost":0,"modeac":0,"modes":3285458,"bad":2276326,"unknown_icao":916261,"accepted":[87059,5812],"signal":-10.4,"noise":-21.4,"peak_signal":-1.0,"strong_signals":4214,
"pre_phase_1":[ 2015512, 2015512, 1898810, 1898810, 1909340],"best_phase" :[ 17969, 17693, 18158, 19706, 19345]},"messages_valid": 92871,"position_count_total": 10209,"position_count_by_type": {"adsb_icao": 10209,"adsb_icao_nt": 0,"adsr_icao": 0,"tisb_icao": 0,"adsc": 0,"mlat": 0,"other": 0,"mode_s": 0,"adsb_other": 0,"adsr_other": 0,"tisb_trackfile": 0,"tisb_other": 0,"mode_ac": 0,"unknown": 0},"remote":{"modeac":0,"modes":0,"basestation": 0,"bad":0,"unknown_icao":0,"accepted":[0,0],"bytes_in": 0,"bytes_out": 872908},"cpr":{"surface":2,"airborne":10229,"global_ok":10091,"global_bad":0,"global_range":0,"global_speed":0,"global_skipped":14,"local_ok":118,"local_aircraft_relative":118,"local_receiver_relative":0,"local_skipped":22,"local_range":0,"local_speed":0,"filtered":0},"altitude_suppressed":0,"cpu":{"demod":11547,"reader":3835,"background":296,"aircraft_json":471,"globe_json":0,"binCraft":0,"trace_json":0,"heatmap_and_state":45,"api_workers":0,"api_update":1065,"remove_stale":243},"tracks":{"all":12,"single_message":1},"messages":92871,"max_distance":446765},
"last5min":{"start":1782178595.0,"end":1782178895.0,"local":{"samples_processed":719978496,"samples_dropped":0,"samples_lost":0,"modeac":0,"modes":16490185,"bad":11425015,"unknown_icao":4605993,"accepted":[430455,28722],"signal":-10.6,"noise":-21.4,"peak_signal":-0.9,"strong_signals":19620,
"pre_phase_1":[ 10112100, 10112100, 9531793, 9531793, 9578775],"best_phase" :[ 89444, 87147, 90381, 97022, 95183]},"messages_valid": 459177,"position_count_total": 50421,"position_count_by_type": {"adsb_icao": 50421,"adsb_icao_nt": 0,"adsr_icao": 0,"tisb_icao": 0,"adsc": 0,"mlat": 0,"other": 0,"mode_s": 0,"adsb_other": 0,"adsr_other": 0,"tisb_trackfile": 0,"tisb_other": 0,"mode_ac": 0,"unknown": 0},"remote":{"modeac":0,"modes":0,"basestation": 0,"bad":0,"unknown_icao":0,"accepted":[0,0],"bytes_in": 0,"bytes_out": 4319915},"cpr":{"surface":3,"airborne":50507,"global_ok":49958,"global_bad":2,"global_range":0,"global_speed":0,"global_skipped":71,"local_ok":463,"local_aircraft_relative":463,"local_receiver_relative":0,"local_skipped":87,"local_range":0,"local_speed":0,"filtered":0},"altitude_suppressed":0,"cpu":{"demod":57949,"reader":19080,"background":1486,"aircraft_json":2364,"globe_json":0,"binCraft":0,"trace_json":0,"heatmap_and_state":225,"api_workers":0,"api_update":5283,"remove_stale":1129},"tracks":{"all":53,"single_message":12},"messages":459177,"max_distance":454046},
"last15min":{"start":1782177995.0,"end":1782178895.0,"local":{"samples_processed":2160066560,"samples_dropped":0,"samples_lost":0,"modeac":0,"modes":49061468,"bad":34049608,"unknown_icao":13659166,"accepted":[1266285,86409],"signal":-10.8,"noise":-21.7,"peak_signal":-0.9,"strong_signals":56266,
"pre_phase_1":[ 30018136, 30018136, 28277354, 28277354, 28326675],"best_phase" :[ 262610, 258069, 266847, 284942, 280226]},"messages_valid": 1352694,"position_count_total": 149381,"position_count_by_type": {"adsb_icao": 149381,"adsb_icao_nt": 0,"adsr_icao": 0,"tisb_icao": 0,"adsc": 0,"mlat": 0,"other": 0,"mode_s": 0,"adsb_other": 0,"adsr_other": 0,"tisb_trackfile": 0,"tisb_other": 0,"mode_ac": 0,"unknown": 0},"remote":{"modeac":0,"modes":0,"basestation": 0,"bad":0,"unknown_icao":0,"accepted":[0,0],"bytes_in": 0,"bytes_out": 12894590},"cpr":{"surface":6,"airborne":149674,"global_ok":147753,"global_bad":12,"global_range":6,"global_speed":2,"global_skipped":238,"local_ok":1628,"local_aircraft_relative":1628,"local_receiver_relative":0,"local_skipped":287,"local_range":1,"local_speed":0,"filtered":0},"altitude_suppressed":0,"cpu":{"demod":172384,"reader":56906,"background":4472,"aircraft_json":7172,"globe_json":0,"binCraft":0,"trace_json":0,"heatmap_and_state":674,"api_workers":0,"api_update":16092,"remove_stale":3344},"tracks":{"all":160,"single_message":45},"messages":1352694,"max_distance":462729},
"total":{"start":1782097959.7,"end":1782178895.0,"local":{"samples_processed":194243067904,"samples_dropped":0,"samples_lost":0,"modeac":0,"modes":3549949242,"bad":2462684261,"unknown_icao":997957335,"accepted":[83566205,5741441],"signal":-11.0,"noise":-23.4,"peak_signal":-0.6,"strong_signals":3351881,
"pre_phase_1":[2129021812,2129021812,2044182654,2044182654,1964392369],"best_phase" :[ 17080206, 17195675, 17611069, 18808825, 18611871]},"messages_valid": 89307646,"position_count_total": 10405446,"position_count_by_type": {"adsb_icao": 10405434,"adsb_icao_nt": 0,"adsr_icao": 12,"tisb_icao": 0,"adsc": 0,"mlat": 0,"other": 0,"mode_s": 0,"adsb_other": 0,"adsr_other": 0,"tisb_trackfile": 0,"tisb_other": 0,"mode_ac": 0,"unknown": 0},"remote":{"modeac":0,"modes":0,"basestation": 0,"bad":0,"unknown_icao":0,"accepted":[0,0],"bytes_in": 0,"bytes_out": 875820693},"cpr":{"surface":425,"airborne":10429687,"global_ok":10309362,"global_bad":379,"global_range":226,"global_speed":19,"global_skipped":13309,"local_ok":96084,"local_aircraft_relative":96084,"local_receiver_relative":0,"local_skipped":24281,"local_range":5,"local_speed":6,"filtered":0},"altitude_suppressed":0,"cpu":{"demod":13061743,"reader":4915954,"background":397369,"aircraft_json":527937,"globe_json":0,"binCraft":0,"trace_json":0,"heatmap_and_state":59691,"api_workers":0,"api_update":1051844,"remove_stale":259906},"tracks":{"all":11566,"single_message":3607},"messages":89307646,"max_distance":476492}
}
+89
View File
@@ -0,0 +1,89 @@
// Package wingbits provides Go types for the Wingbits-specific station
// endpoints: network diagnostics (/network/diagnostics), Prometheus metrics
// (/metrics) and Tailscale status (/tailscale/status). These types are decode
// targets only; see pkg/client for fetching them from a live station.
package wingbits
import "time"
// Diagnostics is the decoded form of /network/diagnostics: a point-in-time
// snapshot of the station's network configuration and reachability to the
// Wingbits backend, used to troubleshoot feeders.
type Diagnostics struct {
// Timestamp is when the diagnostics were collected (on the station).
Timestamp time.Time `json:"timestamp"`
// Hostname is the station's hostname.
Hostname string `json:"hostname"`
// Interfaces lists the station's network interfaces.
Interfaces []Interface `json:"interfaces"`
// DefaultGateway is the IPv4 default gateway.
DefaultGateway string `json:"defaultGateway"`
// DNSServers lists the resolvers in use.
DNSServers []string `json:"dnsServers"`
// DNSResolution records lookups of the Wingbits service hostnames.
DNSResolution []DNSResult `json:"dnsResolution"`
// Connectivity records reachability probes to backend targets.
Connectivity []ConnectivityResult `json:"connectivity"`
// RouteInfo is the raw routing table (`ip route`) output.
RouteInfo string `json:"routeInfo"`
// ConnectionStats summarizes local socket states.
ConnectionStats ConnectionStats `json:"connectionStats"`
}
// Interface describes a single network interface.
type Interface struct {
Name string `json:"name"`
Addresses []string `json:"addresses"`
MAC string `json:"mac,omitempty"`
Flags []string `json:"flags"`
MTU int `json:"mtu"`
}
// DNSResult is the outcome of resolving a hostname, with latency in milliseconds.
type DNSResult struct {
Hostname string `json:"hostname"`
Resolved bool `json:"resolved"`
Addresses []string `json:"addresses"`
LatencyMS float64 `json:"latency"`
}
// ConnectivityResult is the outcome of a reachability probe to a target.
type ConnectivityResult struct {
// Target is the probed endpoint (URL or host:port).
Target string `json:"target"`
// Type is the probe kind, "http" or "tcp".
Type string `json:"type"`
// Reachable reports whether the probe succeeded.
Reachable bool `json:"reachable"`
// LatencyMS is the total time to complete the probe, in milliseconds.
LatencyMS float64 `json:"latency"`
// Breakdown splits the latency into connection phases (milliseconds).
Breakdown LatencyBreakdown `json:"breakdown"`
}
// LatencyBreakdown splits probe latency into phases, in milliseconds. TLS and
// TTFB are zero for plain TCP probes.
type LatencyBreakdown struct {
DNS float64 `json:"dns"`
Connect float64 `json:"connect"`
TLS float64 `json:"tls,omitempty"`
TTFB float64 `json:"ttfb,omitempty"`
}
// ConnectionStats summarizes TCP socket states on the station.
type ConnectionStats struct {
Established int `json:"established"`
TimeWait int `json:"timeWait"`
CloseWait int `json:"closeWait"`
Listen int `json:"listen"`
}
// AllReachable reports whether every connectivity probe succeeded.
func (d Diagnostics) AllReachable() bool {
for _, p := range d.Connectivity {
if !p.Reachable {
return false
}
}
return true
}
+133
View File
@@ -0,0 +1,133 @@
package wingbits
import (
"bufio"
"io"
"strconv"
"strings"
)
// Metrics is the parsed /metrics endpoint: the Prometheus text exposition
// format decoded into metric families keyed by name. Use Value/Sample for raw
// access or Wingbits for the station-specific feeder metrics.
type Metrics struct {
Families map[string]*MetricFamily
}
// MetricFamily groups all samples sharing a metric name with its HELP/TYPE.
type MetricFamily struct {
Name string
Help string
Type string
Samples []Sample
}
// Sample is a single observation: a value with its label set.
type Sample struct {
Labels map[string]string
Value float64
}
// Value returns the value of the first (typically only) sample of a metric.
func (m *Metrics) Value(name string) (float64, bool) {
if s := m.Sample(name); s != nil {
return s.Value, true
}
return 0, false
}
// Sample returns the first sample of the named metric, or nil if absent.
func (m *Metrics) Sample(name string) *Sample {
if f := m.Families[name]; f != nil && len(f.Samples) > 0 {
return &f.Samples[0]
}
return nil
}
// ParseMetrics decodes the Prometheus text exposition format. Comment lines set
// the current family's HELP/TYPE; other lines are samples.
func ParseMetrics(r io.Reader) (*Metrics, error) {
m := &Metrics{Families: map[string]*MetricFamily{}}
sc := bufio.NewScanner(r)
sc.Buffer(make([]byte, 0, 64*1024), 1<<20)
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == "" {
continue
}
if strings.HasPrefix(line, "#") {
parseMeta(m, line)
continue
}
parseSample(m, line)
}
return m, sc.Err()
}
// parseMeta handles "# HELP name text" and "# TYPE name type" lines.
func parseMeta(m *Metrics, line string) {
fields := strings.SplitN(strings.TrimLeft(line, "# "), " ", 3)
if len(fields) < 3 {
return
}
kind, name, rest := fields[0], fields[1], fields[2]
f := m.family(name)
switch kind {
case "HELP":
f.Help = rest
case "TYPE":
f.Type = rest
}
}
// parseSample handles a "name{labels} value" line.
func parseSample(m *Metrics, line string) {
name, labels, rest := splitSample(line)
valStr := strings.Fields(rest)
if len(valStr) == 0 {
return
}
val, err := strconv.ParseFloat(valStr[0], 64)
if err != nil {
return
}
f := m.family(name)
f.Samples = append(f.Samples, Sample{Labels: labels, Value: val})
}
// splitSample separates a sample line into metric name, labels and the remainder
// (which begins with the value).
func splitSample(line string) (name string, labels map[string]string, rest string) {
if i := strings.IndexByte(line, '{'); i >= 0 {
j := strings.IndexByte(line, '}')
if j > i {
return line[:i], parseLabels(line[i+1 : j]), strings.TrimSpace(line[j+1:])
}
}
if n, v, ok := strings.Cut(line, " "); ok {
return n, nil, strings.TrimSpace(v)
}
return line, nil, ""
}
// parseLabels parses a comma-separated key="value" label list.
func parseLabels(s string) map[string]string {
out := map[string]string{}
for pair := range strings.SplitSeq(s, ",") {
k, v, ok := strings.Cut(pair, "=")
if !ok {
continue
}
out[strings.TrimSpace(k)] = strings.Trim(strings.TrimSpace(v), `"`)
}
return out
}
func (m *Metrics) family(name string) *MetricFamily {
f, ok := m.Families[name]
if !ok {
f = &MetricFamily{Name: name}
m.Families[name] = f
}
return f
}
+79
View File
@@ -0,0 +1,79 @@
package wingbits
// WingbitsMetrics is a typed projection of the wingbits_* metric families: the
// feeder's own telemetry about its beast input, batching, NATS upload and
// self-updater. Fields are zero when the corresponding metric is absent.
type WingbitsMetrics struct {
// Version is the running feeder version, from the wingbits_version label.
Version string
// Beast covers the raw Mode S (Beast) input pipeline.
Beast BeastMetrics
// NATS covers publishing decoded data to the Wingbits backend.
NATS NATSMetrics
// Updater covers the self-update checker.
Updater UpdaterMetrics
}
// BeastMetrics describes the Beast message input and batching pipeline.
type BeastMetrics struct {
Connections float64 // active beast connections
Received float64 // total messages received
Dropped float64 // total messages dropped
Invalid float64 // total invalid messages
Buffered float64 // messages currently buffered
Batches float64 // total batches published to the queue
LastBatch float64 // size of the last batch published
BatchesSent float64 // total batches sent to the server
}
// NATSMetrics describes the upload link to the Wingbits backend.
type NATSMetrics struct {
Connected bool // whether the NATS connection is active
Sent float64 // total messages published
Dropped float64 // total messages that failed to publish
Reconnects float64 // total reconnections
AuthFailures float64 // total authentication failures
}
// UpdaterMetrics describes the self-updater.
type UpdaterMetrics struct {
SecondsUntilNextCheck float64 // seconds until the next update check
ConsecutiveFailures float64 // consecutive self-update failures
}
// Wingbits projects the wingbits_* families into a typed view.
func (m *Metrics) Wingbits() WingbitsMetrics {
v := func(name string) float64 { val, _ := m.Value(name); return val }
return WingbitsMetrics{
Version: m.versionLabel(),
Beast: BeastMetrics{
Connections: v("wingbits_beast_connections"),
Received: v("wingbits_beast_received_total"),
Dropped: v("wingbits_beast_dropped_total"),
Invalid: v("wingbits_beast_invalid_total"),
Buffered: v("wingbits_beast_buffered"),
Batches: v("wingbits_beast_batches_total"),
LastBatch: v("wingbits_beast_batch_size"),
BatchesSent: v("wingbits_beast_sent_total"),
},
NATS: NATSMetrics{
Connected: v("wingbits_nats_connected") != 0,
Sent: v("wingbits_nats_sent_total"),
Dropped: v("wingbits_nats_dropped_total"),
Reconnects: v("wingbits_nats_reconnects_total"),
AuthFailures: v("wingbits_nats_auth_failures_total"),
},
Updater: UpdaterMetrics{
SecondsUntilNextCheck: v("wingbits_updater_seconds_until_next_update_check"),
ConsecutiveFailures: v("wingbits_updater_consecutive_failures"),
},
}
}
// versionLabel extracts the version label from the wingbits_version metric.
func (m *Metrics) versionLabel() string {
if s := m.Sample("wingbits_version"); s != nil {
return s.Labels["version"]
}
return ""
}
+16
View File
@@ -0,0 +1,16 @@
package wingbits
// TailscaleStatus is the decoded form of /tailscale/status. The station uses
// Tailscale for remote access; this reports whether that link is up.
type TailscaleStatus struct {
// Running reports whether the tailscaled daemon is running.
Running bool `json:"running"`
// State is the backend state, e.g. "Running", "Stopped", "NeedsLogin".
State string `json:"state"`
// Hostname is the station's Tailscale machine name.
Hostname string `json:"hostname"`
// Online reports whether the node is currently reachable on the tailnet.
Online bool `json:"online"`
// TailscaleIPs lists the node's tailnet addresses, when connected.
TailscaleIPs []string `json:"tailscaleIPs,omitempty"`
}
+1
View File
@@ -0,0 +1 @@
{"timestamp":"2026-06-22T21:41:41.454648308-04:00","hostname":"wb","interfaces":[{"name":"lo","addresses":["127.0.0.1/8"],"flags":["up","running","loopback"],"mtu":65536},{"name":"eth0","addresses":["192.168.0.127/24"],"mac":"70:b3:d5:f2:e0:00","flags":["up","running"],"mtu":1500},{"name":"wlan0","addresses":["192.168.0.144/24"],"mac":"40:d9:5a:8a:76:48","flags":["up","running"],"mtu":1500}],"defaultGateway":"192.168.0.1","dnsServers":["1.1.1.1","1.0.0.1"],"dnsResolution":[{"hostname":"ingest.wingbits.com","resolved":true,"addresses":["54.155.10.204"],"latency":40.100221999999995},{"hostname":"internal-api.wingbits.com","resolved":true,"addresses":["108.129.68.124","34.248.152.241","63.32.148.62"],"latency":36.120657},{"hostname":"install.wingbits.com","resolved":true,"addresses":["18.67.65.37","18.67.65.60","18.67.65.10","18.67.65.2"],"latency":37.321098},{"hostname":"www.google.com","resolved":true,"addresses":["142.251.154.119","142.251.152.119","142.251.157.119","142.251.150.119","142.251.156.119","142.251.153.119","142.251.151.119","142.251.155.119","2001:4860:482b:7700::","2001:4860:482c:7700::","2001:4860:482d:7700::","2001:4860:4829:7700::","2001:4860:4827:7700::","2001:4860:4828:7700::","2001:4860:4826:7700::","2001:4860:482a:7700::"],"latency":112.16570800000001}],"connectivity":[{"target":"https://internal-api.wingbits.com","type":"http","reachable":true,"latency":628.819459,"breakdown":{"dns":34.805678,"connect":120.534413,"tls":338.13093100000003,"ttfb":628.3997569999999}},{"target":"ingest.wingbits.com:443","type":"tcp","reachable":true,"latency":189.408614,"breakdown":{"dns":29.209763,"connect":160.178892}},{"target":"https://www.google.com","type":"http","reachable":true,"latency":337.808852,"breakdown":{"dns":103.613046,"connect":38.71691,"tls":145.868652,"ttfb":337.312568}}],"routeInfo":"default via 192.168.0.1 dev eth0 metric 10 \ndefault via 192.168.0.1 dev wlan0 metric 100 \n192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.127 \n192.168.0.0/24 dev wlan0 proto kernel scope link src 192.168.0.144","connectionStats":{"established":29,"timeWait":0,"closeWait":0,"listen":16}}
+102
View File
@@ -0,0 +1,102 @@
# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use
# TYPE go_memstats_alloc_bytes gauge
go_memstats_alloc_bytes 4070856
# HELP go_memstats_total_alloc_bytes Total number of bytes allocated (even if freed)
# TYPE go_memstats_total_alloc_bytes counter
go_memstats_total_alloc_bytes 20778988576
# HELP go_memstats_sys_bytes Number of bytes obtained from system
# TYPE go_memstats_sys_bytes gauge
go_memstats_sys_bytes 24206460
# HELP go_memstats_heap_alloc_bytes Number of heap bytes allocated and still in use
# TYPE go_memstats_heap_alloc_bytes gauge
go_memstats_heap_alloc_bytes 4070856
# HELP go_memstats_heap_sys_bytes Number of heap bytes obtained from system
# TYPE go_memstats_heap_sys_bytes gauge
go_memstats_heap_sys_bytes 19988480
# HELP go_memstats_heap_idle_bytes Number of heap bytes waiting to be used
# TYPE go_memstats_heap_idle_bytes gauge
go_memstats_heap_idle_bytes 13434880
# HELP go_memstats_heap_inuse_bytes Number of heap bytes in use
# TYPE go_memstats_heap_inuse_bytes gauge
go_memstats_heap_inuse_bytes 6553600
# HELP go_memstats_heap_released_bytes Number of heap bytes released to OS
# TYPE go_memstats_heap_released_bytes gauge
go_memstats_heap_released_bytes 12345344
# HELP go_memstats_heap_objects Number of allocated heap objects
# TYPE go_memstats_heap_objects gauge
go_memstats_heap_objects 29686
# HELP go_memstats_stack_inuse_bytes Number of bytes in use by the stack allocator
# TYPE go_memstats_stack_inuse_bytes gauge
go_memstats_stack_inuse_bytes 983040
# HELP go_memstats_stack_sys_bytes Number of bytes obtained from system for stack allocator
# TYPE go_memstats_stack_sys_bytes gauge
go_memstats_stack_sys_bytes 983040
# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata
# TYPE go_memstats_gc_sys_bytes gauge
go_memstats_gc_sys_bytes 1500288
# HELP go_memstats_next_gc_bytes Number of heap bytes when next garbage collection will take place
# TYPE go_memstats_next_gc_bytes gauge
go_memstats_next_gc_bytes 5774786
# HELP go_memstats_last_gc_time_seconds Number of seconds since 1970 of last garbage collection
# TYPE go_memstats_last_gc_time_seconds gauge
go_memstats_last_gc_time_seconds 1782178901
# HELP go_memstats_num_gc Number of completed GC cycles
# TYPE go_memstats_num_gc counter
go_memstats_num_gc 10191
# HELP go_goroutines Number of goroutines that currently exist
# TYPE go_goroutines gauge
go_goroutines 72
# HELP process_rlimit_as_soft_bytes Soft limit for address space (RLIMIT_AS)
# TYPE process_rlimit_as_soft_bytes gauge
process_rlimit_as_soft_bytes 204800000
# HELP process_rlimit_as_hard_bytes Hard limit for address space (RLIMIT_AS)
# TYPE process_rlimit_as_hard_bytes gauge
process_rlimit_as_hard_bytes 204800000
# HELP wingbits_beast_connections Number of active beast connections
# TYPE wingbits_beast_connections gauge
wingbits_beast_connections 1
# HELP wingbits_beast_received_total Total messages received from beast
# TYPE wingbits_beast_received_total counter
wingbits_beast_received_total 41937651
# HELP wingbits_beast_dropped_total Total messages dropped from beast
# TYPE wingbits_beast_dropped_total counter
wingbits_beast_dropped_total 0
# HELP wingbits_beast_invalid_total Total invalid messages from beast
# TYPE wingbits_beast_invalid_total counter
wingbits_beast_invalid_total 0
# HELP wingbits_beast_buffered Beast messages currently buffered
# TYPE wingbits_beast_buffered gauge
wingbits_beast_buffered 2
# HELP wingbits_beast_batches_total Total beast batches published to queue
# TYPE wingbits_beast_batches_total counter
wingbits_beast_batches_total 884452
# HELP wingbits_beast_batch_size Size of last beast batch published
# TYPE wingbits_beast_batch_size gauge
wingbits_beast_batch_size 92
# HELP wingbits_beast_sent_total Total beast batches sent to server
# TYPE wingbits_beast_sent_total counter
wingbits_beast_sent_total 884036
# HELP wingbits_updater_seconds_until_next_update_check Seconds until next update check
# TYPE wingbits_updater_seconds_until_next_update_check gauge
wingbits_updater_seconds_until_next_update_check 780
# HELP wingbits_updater_consecutive_failures Number of consecutive self-update failures since the last success
# TYPE wingbits_updater_consecutive_failures gauge
wingbits_updater_consecutive_failures 0
# HELP wingbits_nats_sent_total Total messages published to NATS
# TYPE wingbits_nats_sent_total counter
wingbits_nats_sent_total 884036
# HELP wingbits_nats_dropped_total Total messages failed to publish to NATS
# TYPE wingbits_nats_dropped_total counter
wingbits_nats_dropped_total 416
# HELP wingbits_nats_reconnects_total Total NATS reconnections
# TYPE wingbits_nats_reconnects_total counter
wingbits_nats_reconnects_total 22
# HELP wingbits_nats_auth_failures_total Total NATS authentication failures
# TYPE wingbits_nats_auth_failures_total counter
wingbits_nats_auth_failures_total 0
# HELP wingbits_nats_connected Whether the NATS connection is active
# TYPE wingbits_nats_connected gauge
wingbits_nats_connected 1
# HELP wingbits_version Version number
# TYPE wingbits_version gauge
wingbits_version{version="v1.12.1"} 1
+1
View File
@@ -0,0 +1 @@
{"running":false,"state":"Stopped","hostname":"wingbits-01232815ae5f7464ee","online":false}
+64
View File
@@ -0,0 +1,64 @@
package wingbits
import (
"encoding/json"
"os"
"testing"
)
func TestDecodeDiagnostics(t *testing.T) {
b, err := os.ReadFile("testdata/diagnostics.json")
if err != nil {
t.Fatal(err)
}
var d Diagnostics
if err := json.Unmarshal(b, &d); err != nil {
t.Fatal(err)
}
if d.Hostname == "" || len(d.Interfaces) == 0 || len(d.Connectivity) == 0 {
t.Errorf("diagnostics not decoded: %+v", d)
}
if d.Timestamp.IsZero() {
t.Error("timestamp not decoded")
}
}
func TestDecodeStatus(t *testing.T) {
b, err := os.ReadFile("testdata/status.json")
if err != nil {
t.Fatal(err)
}
var s TailscaleStatus
if err := json.Unmarshal(b, &s); err != nil {
t.Fatal(err)
}
if s.Hostname == "" || s.State == "" {
t.Errorf("status not decoded: %+v", s)
}
}
func TestParseMetrics(t *testing.T) {
f, err := os.Open("testdata/metrics.txt")
if err != nil {
t.Fatal(err)
}
defer f.Close()
m, err := ParseMetrics(f)
if err != nil {
t.Fatal(err)
}
if _, ok := m.Value("wingbits_beast_received_total"); !ok {
t.Error("missing beast received metric")
}
wb := m.Wingbits()
if wb.Version == "" {
t.Error("version label not parsed")
}
if !wb.NATS.Connected || wb.Beast.Received == 0 {
t.Errorf("wingbits view not populated: %+v", wb)
}
// Go runtime families should also be present and typed.
if f := m.Families["go_memstats_alloc_bytes"]; f == nil || f.Type != "gauge" {
t.Error("expected go_memstats_alloc_bytes gauge family")
}
}