├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── go.mod ├── go.sum ├── gui.go └── main.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | name: Build and test 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Setup Go 17 | uses: actions/setup-go@v1 18 | with: 19 | go-version: 1.13 20 | - name: Checkout code 21 | uses: actions/checkout@v2 22 | - name: go get 23 | run: go get 24 | - name: go test 25 | run: go test 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | generate: 10 | name: Generate cross-platform builds 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout the repository 14 | uses: actions/checkout@v2 15 | - name: Generate build files 16 | uses: thatisuday/go-build-action@v1 17 | with: 18 | platforms: "linux/amd64, linux/arm, darwin/amd64" 19 | name: "flightmon" 20 | compress: "true" 21 | dest: "dist" 22 | - name: Upload build-artifacts 23 | uses: skx/github-action-publish-binaries@master 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | args: "./dist/*" 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### v0.1.2 (2020-06-13) 4 | 5 | * Internal cleanups. 6 | 7 | ### v0.1.1 (2020-06-12) 8 | 9 | * Better error handling. 10 | 11 | ### v0.1.0 (2020-06-11) 12 | 13 | * Initial release! 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 mike wakerly 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flightmon 2 | 3 | A command-line "GUI" for watching ADS-B output from a dump1090 daemon. 4 | 5 | ![Screenshot of Flightmon in action](https://user-images.githubusercontent.com/390829/84444664-48db3080-ac10-11ea-994c-3910e9f382fd.png) 6 | 7 | Test on Mac OS X, Linux amd64, and Linux arm (e.g. Raspberry Pi). 8 | 9 | ## Usage 10 | 11 | To run it, just type `flightmon`. Your terminal will be taken over with a lovely table based on the SBS1 data stream coming from `localhost:30003`. You can specify a different host with `--dump_host`. 12 | 13 | Full usage instructions: 14 | 15 | ``` 16 | Usage flightmon: 17 | -debug 18 | Enable debug output 19 | -dump_host string 20 | dump1090 SBS1 stream address (required) (default "localhost:30003") 21 | -max_age int 22 | Stop showing aircraft older than this many seconds (default 60) 23 | -show_ui 24 | Enable the UI (default true) 25 | ``` 26 | 27 | ## Download 28 | 29 | Head on over to the [releases page](https://github.com/mik3y/flightmon/releases) for pre-built binaries for Linux and Mac. 30 | 31 | ## Changelog 32 | 33 | See [CHANGELOG.md](https://github.com/mik3y/flightmon/blob/master/CHANGELOG.md) for the latest changes. 34 | 35 | ## Contributing 36 | 37 | Contributions welcome! Open an issue or a pull request if you've got ideas. 38 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mik3y/flightmon 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gdamore/tcell v1.3.0 7 | github.com/hashicorp/golang-lru v0.5.4 8 | github.com/ipfs/go-log v1.0.4 9 | github.com/ipfs/go-log/v2 v2.1.1 // indirect 10 | github.com/mattn/go-runewidth v0.0.9 // indirect 11 | github.com/mik3y/goadsb v0.0.0-20200612215427-b90bf2711f33 12 | github.com/rivo/tview v0.0.0-20200528200248-fe953220389f 13 | go.uber.org/zap v1.15.0 // indirect 14 | golang.org/x/sys v0.0.0-20200610111108-226ff32320da // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= 5 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 6 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 11 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 12 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 13 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 14 | github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM= 15 | github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= 16 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 17 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 18 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 19 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 20 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 21 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 22 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 23 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 24 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 25 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 26 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 27 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 28 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 29 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 30 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 31 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 32 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 33 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= 34 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 35 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 36 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 37 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 38 | github.com/ipfs/go-log v1.0.4 h1:6nLQdX4W8P9yZZFH7mO+X/PzjN8Laozm/lMJ6esdgzY= 39 | github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= 40 | github.com/ipfs/go-log/v2 v2.0.5 h1:fL4YI+1g5V/b1Yxr1qAiXTMg1H8z9vx/VmJxBuQMHvU= 41 | github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= 42 | github.com/ipfs/go-log/v2 v2.1.1 h1:G4TtqN+V9y9HY9TA6BwbCVyyBZ2B9MbCjR2MtGx8FR0= 43 | github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= 44 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 45 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 46 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 47 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 48 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 49 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 50 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 51 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 52 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 53 | github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= 54 | github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= 55 | github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 56 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 57 | github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= 58 | github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 59 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 60 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 61 | github.com/mik3y/goadsb v0.0.0-20200612215427-b90bf2711f33 h1:bBH8E7dPlqd3k2n2/Cl6lYeCUJlRn/UG7ShbpyXvFSs= 62 | github.com/mik3y/goadsb v0.0.0-20200612215427-b90bf2711f33/go.mod h1:QnH4vvoMhBQ3YNQH3ptDKAUIIlps9FSkn5y1AVbS2YA= 63 | github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= 64 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 65 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 66 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 67 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 68 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 69 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 70 | github.com/rivo/tview v0.0.0-20200528200248-fe953220389f h1:tRx/LLIP2PSA7johw9xhf+6NUCLC4BbMhpGdm110MGI= 71 | github.com/rivo/tview v0.0.0-20200528200248-fe953220389f/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= 72 | github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= 73 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 74 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 75 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 76 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 77 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 78 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 79 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= 80 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 81 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= 82 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 83 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= 84 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 85 | go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo= 86 | go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= 87 | go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= 88 | go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= 89 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 90 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 91 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 92 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 93 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 94 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 95 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= 96 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 97 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 98 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 99 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 100 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 101 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 102 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 103 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 104 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 105 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 106 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 107 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 108 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 109 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 110 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 111 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 112 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo= 113 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 114 | golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38= 115 | golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 116 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 117 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 118 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 119 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 120 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 121 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 122 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 123 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 124 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 125 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 126 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 127 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= 128 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 129 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 130 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 131 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 132 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 133 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 134 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 135 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 136 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 137 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 138 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 139 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 140 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 141 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 142 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 143 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 144 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 145 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 146 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 147 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 148 | google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= 149 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 150 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 151 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 152 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 153 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 154 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 155 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 156 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 157 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 158 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= 159 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 160 | -------------------------------------------------------------------------------- /gui.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/gdamore/tcell" 9 | lru "github.com/hashicorp/golang-lru" 10 | pb "github.com/mik3y/goadsb/proto" 11 | "github.com/rivo/tview" 12 | ) 13 | 14 | var updatedTextColor = tcell.ColorWhite 15 | var normalTextColor = tcell.ColorGray 16 | 17 | func getHeaderText(cache *lru.Cache) string { 18 | return fmt.Sprintf("Flightmon v%s · %d tracked", Version, cache.Len()) 19 | } 20 | 21 | func updateRow(table *tview.Table, row int, positionData *pb.PositionUpdate) { 22 | var cell *tview.TableCell 23 | colNum := 0 24 | 25 | cell, colNum = table.GetCell(row, colNum), colNum+1 26 | cell.SetText(*positionData.IcaoId) 27 | 28 | now := int64(time.Now().UnixNano() / int64(time.Millisecond)) 29 | ageSeconds := (now - *positionData.Timestamp) / 1000 30 | cell, colNum = table.GetCell(row, colNum), colNum+1 31 | cell.SetText(fmt.Sprint(ageSeconds)) 32 | 33 | cell, colNum = table.GetCell(row, colNum), colNum+1 34 | if positionData.Callsign != nil { 35 | if cell.Text != *positionData.Callsign { 36 | cell.SetText(*positionData.Callsign).SetTextColor(updatedTextColor) 37 | } 38 | } 39 | cell, colNum = table.GetCell(row, colNum), colNum+1 40 | if positionData.Lat != nil { 41 | s := fmt.Sprint(*positionData.Lat) 42 | if cell.Text != s { 43 | cell.SetText(s).SetTextColor(updatedTextColor) 44 | } 45 | } 46 | cell, colNum = table.GetCell(row, colNum), colNum+1 47 | if positionData.Lng != nil { 48 | s := fmt.Sprint(*positionData.Lng) 49 | if cell.Text != s { 50 | cell.SetText(s).SetTextColor(updatedTextColor) 51 | } 52 | } 53 | cell, colNum = table.GetCell(row, colNum), colNum+1 54 | if positionData.Altitude != nil { 55 | s := fmt.Sprint(*positionData.Altitude) 56 | if cell.Text != s { 57 | cell.SetText(s).SetTextColor(updatedTextColor) 58 | } 59 | } 60 | cell, colNum = table.GetCell(row, colNum), colNum+1 61 | if positionData.GroundSpeed != nil { 62 | s := fmt.Sprint(*positionData.GroundSpeed) 63 | if cell.Text != s { 64 | cell.SetText(s).SetTextColor(updatedTextColor) 65 | } 66 | } 67 | cell, colNum = table.GetCell(row, colNum), colNum+1 68 | if positionData.Track != nil { 69 | s := fmt.Sprint(*positionData.Track) 70 | if cell.Text != s { 71 | cell.SetText(s).SetTextColor(updatedTextColor) 72 | } 73 | } 74 | cell, colNum = table.GetCell(row, colNum), colNum+1 75 | if positionData.VerticalRate != nil { 76 | s := fmt.Sprint(*positionData.VerticalRate) 77 | if cell.Text != s { 78 | cell.SetText(s).SetTextColor(updatedTextColor) 79 | } 80 | } 81 | cell, colNum = table.GetCell(row, colNum), colNum+1 82 | if positionData.Squawk != nil { 83 | if cell.Text != *positionData.Squawk { 84 | cell.SetText(*positionData.Squawk).SetTextColor(updatedTextColor) 85 | } 86 | } 87 | } 88 | 89 | func clearUpdatedColors(table *tview.Table, row int) { 90 | for c := 0; c < table.GetColumnCount(); c++ { 91 | table.GetCell(row, c).SetTextColor(normalTextColor) 92 | } 93 | } 94 | 95 | func updateTable(cache *lru.Cache, table *tview.Table, header *tview.TextView) { 96 | // Clear any previously-updated cells. 97 | for r := 1; r < table.GetRowCount(); r++ { 98 | clearUpdatedColors(table, r) 99 | } 100 | 101 | // Pass 1: Refresh all existing rows. 102 | updatedMap := make(map[string]bool) 103 | for r := table.GetRowCount(); r >= 1; r-- { 104 | icaoID := table.GetCell(r, 0).Text 105 | updatedMap[icaoID] = true 106 | positionDataPtr, ok := cache.Peek(icaoID) 107 | if !ok { 108 | table.RemoveRow(r) 109 | } else { 110 | positionData := positionDataPtr.(*pb.PositionUpdate) 111 | updateRow(table, r, positionData) 112 | } 113 | } 114 | 115 | // Pass 2: Add any new rows. 116 | keyItems := cache.Keys() 117 | keys := make([]string, len(keyItems)) 118 | for i, v := range keyItems { 119 | keys[i] = fmt.Sprint(v) 120 | } 121 | 122 | for idx := range keys { 123 | icaoID := keys[idx] 124 | if updatedMap[icaoID] { 125 | continue 126 | } 127 | positionDataPtr, ok := cache.Peek(icaoID) 128 | if !ok { 129 | continue 130 | } 131 | positionData := positionDataPtr.(*pb.PositionUpdate) 132 | rowNum := table.GetRowCount() 133 | for c := 0; c < table.GetColumnCount(); c++ { 134 | table.SetCell(rowNum, c, tview.NewTableCell("").SetTextColor(updatedTextColor)) 135 | } 136 | updateRow(table, rowNum, positionData) 137 | } 138 | 139 | header.SetText(getHeaderText(cache)) 140 | } 141 | 142 | // ShowUI shows a curses-like GUI in the terminal. 143 | func ShowUI(cache *lru.Cache, donec chan<- struct{}) { 144 | app := tview.NewApplication() 145 | 146 | header := tview.NewTextView(). 147 | SetText(getHeaderText(cache)). 148 | SetTextColor(tcell.ColorGreen) 149 | 150 | table := tview.NewTable(). 151 | SetBorders(true) 152 | 153 | grid := tview.NewGrid(). 154 | SetRows(1, 0). 155 | SetBorders(false). 156 | AddItem(header, 0, 0, 1, 1, 0, 0, false). 157 | AddItem(table, 1, 0, 1, 1, 0, 0, true) 158 | 159 | headers := strings.Split("ICAO Age Callsign Lat Lng Altitude GroundSpeed Track VerticalRate Squawk", " ") 160 | for h := 0; h < len(headers); h++ { 161 | table.SetCell(0, h, 162 | tview.NewTableCell(headers[h]). 163 | SetTextColor(tcell.ColorYellow). 164 | SetAlign(tview.AlignCenter)) 165 | } 166 | 167 | table.SetFixed(1, 4) 168 | 169 | go func() { 170 | for { 171 | time.Sleep(time.Second) 172 | updateTable(cache, table, header) 173 | app.Draw() 174 | } 175 | }() 176 | 177 | if err := app.SetRoot(grid, true).SetFocus(table).Run(); err != nil { 178 | panic(err) 179 | } 180 | donec <- struct{}{} 181 | } 182 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | "time" 9 | 10 | lru "github.com/hashicorp/golang-lru" 11 | adsb "github.com/mik3y/goadsb" 12 | pb "github.com/mik3y/goadsb/proto" 13 | 14 | logging "github.com/ipfs/go-log" 15 | ) 16 | 17 | var Version = "0.1.2" 18 | 19 | var dumpHost = flag.String("dump_host", "localhost:30003", "dump1090 SBS1 stream address (required)") 20 | var debug = flag.Bool("debug", false, "Enable debug output") 21 | var showUI = flag.Bool("show_ui", true, "Enable the UI") 22 | var maxAge = flag.Int("max_age", 60, "Stop showing aircraft older than this many seconds") 23 | 24 | var logger = logging.Logger("main") 25 | 26 | func getAgeOfUpdateInSeconds(update *pb.PositionUpdate) int { 27 | now := int64(time.Now().UnixNano() / int64(time.Millisecond)) 28 | ageSeconds := (now - *update.Timestamp) / 1000 29 | return int(ageSeconds) 30 | } 31 | 32 | func startAging(cache *lru.Cache) { 33 | for { 34 | for _, key := range cache.Keys() { 35 | val, _ := cache.Peek(key) 36 | record := val.(*pb.PositionUpdate) 37 | age := getAgeOfUpdateInSeconds(record) 38 | if age >= *maxAge { 39 | logger.Debugf("Removing expired flight: %s", record.IcaoId) 40 | cache.Remove(key) 41 | } 42 | } 43 | time.Sleep(time.Second) 44 | } 45 | 46 | } 47 | 48 | func main() { 49 | flag.Parse() 50 | 51 | if *debug { 52 | logging.SetLogLevel("main", "debug") 53 | } else { 54 | if *showUI { 55 | logging.SetLogLevel("main", "warning") 56 | } else { 57 | logging.SetLogLevel("main", "info") 58 | } 59 | } 60 | 61 | trackedCache, err := lru.New(128) 62 | if err != nil { 63 | panic(err) 64 | } 65 | 66 | updates := make(chan *pb.PositionUpdate, 10) 67 | go adsb.StartSbsTracking(*dumpHost, logger.SugaredLogger, trackedCache, updates) 68 | go startAging(trackedCache) 69 | 70 | stop := make(chan os.Signal, 1) 71 | signal.Notify(stop, syscall.SIGINT) 72 | 73 | donec := make(chan struct{}, 1) 74 | 75 | if *showUI { 76 | go ShowUI(trackedCache, donec) 77 | } 78 | 79 | for { 80 | select { 81 | case <-stop: 82 | os.Exit(0) 83 | case <-donec: 84 | os.Exit(0) 85 | case record := <-updates: 86 | if record == nil { 87 | os.Exit(0) 88 | } 89 | logger.Debugf("PositionUpdate: %v", record) 90 | } 91 | } 92 | } 93 | --------------------------------------------------------------------------------