Raspberry PiとDVB-TでADS-BのFlightradar24フィーダー

提供: ディーズガレージ wiki
移動先: 案内検索

目次

組み立て
ダイヤモンド (第一電波工業) D555 120MHz/1090MHz帯受信専用


この記事は古くなっています。
Jessie with PIXEL版に書き直ししました。

航空機が発信してるADS-Bという信号を受信しFlightradar24に送信する試みです。この行為はFlightradar24公式では日本の電波法には問題ないとしています。違法状態になったら記事削除します。

記事全体に依存する部分でアップデートがあり情報が古くなってます。お勧めしませんがこの記事作成時のdump1090-mutabilityのdebファイルはこちら
$ wget http://dz.plala.jp/wiki_data/dump1090-mutability_1.15~dev_armhf.deb
紛争地域からのアクセスが気になりますので。日本では軍用機がADS-B信号を発信することは稀でミサイル攻撃から人命確保をするには意味を持ちません。各国の事情にご注意ください。

環境

ボード Raspberry Pi2 Model B+
OS 2016-03-18-raspbian-jessie.img
microSDHC SanDisk Ultra microSDHC Class10 8GB
DVB-T シャフトコーポレーション TV28Tv2DVB-T(R820T)
シャフトコーポレーション 放熱プレート+小型ヒートシンク
アンテナ ダイヤモンド D555 120MHz/1090MHz帯受信専用
その他 シャフトコーポレーション MCX-P/SMA-J変換ケーブル 300mm
ダイヤモンド 2D1SR M-SMA変換ケーブル 1m または
ダイヤモンド 2D2SR M-SMA変換ケーブル 2m
ダイヤモンド BK10 モービルアンテナ取付金具

Raspberry Piにモニター、マウス、キーボード、電源、有線LAN、DVB-T、アンテナが接続されている状態から作業をすすめます。

ファームウェア更新

$ sudo rpi-update

再起動

$ sudo reboot

最新状態にアップデート

$ sudo apt-get update
$ sudo apt-get upgrade

再起動

$ sudo reboot

IP固定化

$ sudo leafpad /etc/dhcpcd.conf

末尾に追記(設定内容は各ネットワーク環境により異なります。「Raspberry Pi Jessie IP固定」などで検索してみてください。)

interface eth0
static ip_address=192.168.1.184/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1

再起動

$ sudo reboot

ホスト名

ホスト名で接続できるように設定します。

記事中ではIPアドレスに統一しています。一部のアプリでホスト名で接続できなかったためです。
ホスト名で接続する場合、以下設定を終え記事中の192.168.1.184をfr24.localに置き換えてください。
例 http://192.168.1.184:8082ならhttp://fr24.local:8082

クライアントパソコンにはavahiかBonjourがインストールされている必要があります。
Windowsパソコンだけ問題になりやすく、簡単な方法はiTunesをインストールすることです。
ネット検索で「Raspberry avahi Bonjour」辺りで調べると詳しい事情が出てきます。

ホスト名変更

$ sudo leafpad /etc/hostname

変更

fr24

ホスト名変更

$ sudo leafpad /etc/hosts

変更

127.0.1.1 fr24

再起動

$ sudo reboot

遠隔操作

※外部に公開するwebサーバーでは導入しないでください。非常に危険です。

運用にはモニター、マウス、キーボードは必要ないので、この段階で遠隔操作にしてしまいます。
TightVNCなどではroot(管理者権限)が難しい作りになっています。モニターに表示するX-Windowそのままを表示してくれるx11vncを使用します。

x11vncのインストール

$ sudo apt-get install x11vnc
$ x11vnc -storepasswd
パスワードの入力
保存していいか聞かれるので「y」を選択

起動

$ x11vnc -usepw -forever

Ctrl+Cで一旦停止

自動起動設定

$ cd /home/pi/.config
$ mkdir autostart
$ cd autostart

新規作成

$ sudo leafpad x11vnc.desktop

コピーペースト

[Desktop Entry]
Encoding=UTF-8
Type=Application
Name=X11VNC
Comment=
Exec=x11vnc -forever -display :0 -ultrafilexfer
StartupNotify=false
Terminal=false
Hidden=false

スクリーン解像度を変更する場合

$ sudo leafpad /boot/config.txt

コメントを削除して有効化

framebuffer_width=1440
framebuffer_height=900

シャットダウン

$ sudo shutdown

モニター、マウス、キーボードを取り外して電源入れ直し

VNCクライアントから接続してみる

RealVNC® Enterprise Edition の場合

同じネットワーク内のWindows/Mac/Linux/スマホ/タブレットにVNCクライアントソフトをインストールして接続してみてください。

VNC Server: 192.168.1.184:5900
Password: x11vnc設定時のパスワード

成功していれば、以降はVNCクライアントの窓の中で作業できます。

USB電流供給制限解除

気持ち程度設定します。設定しなくても構わないと思います。
USB電流供給を0.6A制限から1.2Aまで増加できるそうです。

$ sudo leafpad /boot/config.txt

末尾に追加

safe_mode_gpio=4
max_usb_current=1

再起動

$ sudo reboot

NTPデーモン

時間にシビアなシステムです。問題解決の糸口にNTPの設定を加えてみます。
デフォルトでpool.ntp.orgを使用してます。今回はNICT(情報通信研究機構)に変更してみます。

$ sudo leafpad /etc/ntp.conf

変更部分

#server 0.debian.pool.ntp.org iburst
#server 1.debian.pool.ntp.org iburst
#server 2.debian.pool.ntp.org iburst
#server 3.debian.pool.ntp.org iburst
pool ntp.nict.jp iburst

再起動

$ sudo service ntp restart

確認

$ ntpq -p

ウォッチドッグタイマー

Raspberry PiのSoCにはハードウェアとしてWatchDogTimer(WDT)が組み込まれているそうです。
設定してみます。

ネットから拾える情報で設定するとカーネルパニックを起こしてしまいます。
しばらく様子見にします。

dump1090-mutabilityのインストール

dump1090-mutability

参考: dump1090-mutability
オリジナルのDUMP1090は更新されていない古いアプリケーションです。
DUMP1090を現在の需要に合わせ改良されてるものが多く有りますが、ここでは人気のあるdump1090-mutabilityを使ってみます。

インストール(dump1090-mutability_1.15~dev_armhf.debは/home/piディレクトリにコンパイルされたファイル)

$ sudo apt-get install git-core
$ git clone https://github.com/mutability/dump1090.git
$ sudo apt-get install librtlsdr-dev libusb-1.0-0-dev pkg-config debhelper
$ cd dump1090
$ dpkg-buildpackage -b
$ cd ..
$ sudo dpkg -i dump1090-mutability_1.15~dev_armhf.deb

Start dump1090 automatically? <はい>

設定の中で必要な情報を入力する必要があるので先に準備。

  • アンテナの緯度 例: 35.xxxx
  • アンテナの経度 例: 139.xxxx
 $ sudo dpkg-reconfigure dump1090-mutability
Start dump1090 automatically? <はい>
User to run dump1090 as:dump1090 tab押して<了解>
Path to log to:/var/log/dump1090-mutability.log tab押して<了解>
RTL-SDR dongle to use: tab押して<了解>
RTL-SDR gain, in dB:max tab押して<了解>
RTL-SDR frequency correction, in PPM:0 tab押して<了解>
Enable oversampling at 2.4MHz? <はい>
If the maximum range ~長文 <了解>
Absolute maximum range of receiver, in nautical miles:300 tab押して<了解>
Latitude of receiver, in decimal degrees:35.xxxx tab押して<了解> (アンテナの緯度)
Longitude of receiver, in decimal degrees:139.xxxx tab押して<了解> (アンテナの経度)
Port for internal webserver (0 disables):8080 tab押して<了解>
Ports for AVR-format input connections (0 disables):30001 tab押して<了解>
Ports for AVR-format out connections (0 disables):30002 tab押して<了解>
Ports for Beast-format input connections (0 disables):30004,30104 tab押して<了解>
Ports for Beast-format output connections (0 disables):30005 tab押して<了解>
Ports for SBS-format output connections (0 disables):30003 tab押して<了解>
Second between heartbeat messages (0 disables):60 tab押して<了解>
Minimum output message size:500 tab押して<了解>
Maximum output buffering time:1 tab押して<了解>
SO_SNDBUF size:262144 tab押して<了解>
Interface address to bind to (blank for all interfaces):空白にしてtab押して<了解>
Interval between logging stats, in seconds:3600 tab押して<了解>
Interval between writing JSON aircraft state, in seconds:1 tab押して<了解>
exact: dump1090 will provide the exact reciever location. tab押して<了解>
Reciever location accuracy to show in the web interface:approximate tab押して<了解>
Directory to write JSON aircraft state to:空白にしてtab押して<了解>
Log all decoded messages? <いいえ>
Extra argments to pass to dump1090: tab押して<了解>

再起動

$ sudo reboot

動作確認

RaspberryPiの場合 http://127.0.0.1:8080
他のPCの場合 http://192.168.1.184:8080
※なぜかブラウザに直接キー入力しないと開かないブラウザがあります。dump1090のaliasが未設定のように感じます。

メモ 最新版更新方法(dump1090-mutability_1.xx~dev_armhf.debは/home/piディレクトリにコンパイルされたファイル)

$ cd dump1090
$ git pull
$ dpkg-buildpackage -b
$ cd ..
$ sudo dpkg -i dump1090-mutability_1.xx~dev_armhf.deb
$ sudo dpkg-reconfigure dump1090-mutability

メモ dump1090-mutability --help

-----------------------------------------------------------------------------
| dump1090 ModeS Receiver                     dump1090-mutability v1.15~dev |
-----------------------------------------------------------------------------
--device-index <index>   Select RTL device (default: 0)
--gain <db>              Set gain (default: max gain. Use -10 for auto-gain)
--enable-agc             Enable the Automatic Gain Control (default: off)
--freq <hz>              Set frequency (default: 1090 Mhz)
--ifile <filename>       Read data from file (use '-' for stdin)
--iformat <format>       Sample format for --ifile: UC8 (default), SC16, or SC16Q11
--throttle               When reading from a file, play back in realtime, not at max speed
--interactive            Interactive mode refreshing data on screen. Implies --throttle
--interactive-rows <num> Max number of rows in interactive mode (default: 15)
--interactive-ttl <sec>  Remove from list if idle for <sec> (default: 60)
--interactive-rtl1090    Display flight table in RTL1090 format
--raw                    Show only messages hex values
--net                    Enable networking
--modeac                 Enable decoding of SSR Modes 3/A & 3/C
--net-only               Enable just networking, no RTL device or file used
--net-bind-address <ip>  IP address to bind to (default: Any; Use 127.0.0.1 for private)
--net-http-port <ports>  HTTP server ports (default: 8080)
--net-ri-port <ports>    TCP raw input listen ports  (default: 30001)
--net-ro-port <ports>    TCP raw output listen ports (default: 30002)
--net-sbs-port <ports>   TCP BaseStation output listen ports (default: 30003)
--net-bi-port <ports>    TCP Beast input listen ports  (default: 30004,30104)
--net-bo-port <ports>    TCP Beast output listen ports (default: 30005)
--net-ro-size <size>     TCP output minimum size (default: 0)
--net-ro-interval <rate> TCP output memory flush rate in seconds (default: 0)
--net-heartbeat <rate>   TCP heartbeat rate in seconds (default: 60 sec; 0 to disable)
--net-buffer <n>         TCP buffer size 64Kb * (2^n) (default: n=0, 64Kb)
--net-verbatim           Do not apply CRC corrections to messages we forward; send unchanged
--forward-mlat           Allow forwarding of received mlat results to output ports
--lat <latitude>         Reference/receiver latitude for surface posn (opt)
--lon <longitude>        Reference/receiver longitude for surface posn (opt)
--max-range <distance>   Absolute maximum range for position decoding (in nm, default: 300)
--fix                    Enable single-bits error correction using CRC
--no-fix                 Disable single-bits error correction using CRC
--no-crc-check           Disable messages with broken CRC (discouraged)
--phase-enhance          Enable phase enhancement
--mlat                   display raw messages in Beast ascii mode
--stats                  With --ifile print stats at exit. No other output
--stats-range            Collect/show range histogram
--stats-every <seconds>  Show and reset stats every <seconds> seconds
--onlyaddr               Show only ICAO addresses (testing purposes)
--metric                 Use metric units (meters, km/h, ...)
--hae                    Show altitudes as HAE (with H suffix) when available
--snip <level>           Strip IQ file removing samples < level
--debug <flags>          Debug mode (verbose), see README for details
--quiet                  Disable output to stdout. Use for daemon applications
--show-only <addr>       Show only messages from the given ICAO on stdout
--ppm <error>            Set receiver error in parts per million (default 0)
--html-dir <dir>         Use <dir> as base directory for the internal HTTP server. Defaults to /usr/share/dump1090-mutability/html
--write-json <dir>       Periodically write json output to <dir> (for serving by a separate webserver)
--write-json-every <t>   Write json output every t seconds (default 1)
--json-location-accuracy <n>  Accuracy of receiver location in json metadata: 0=no location, 1=approximate, 2=exact
--oversample             Use the 2.4MHz demodulator
--dcfilter               Apply a 1Hz DC filter to input data (requires lots more CPU)
--measure-noise          Measure noise power (requires slightly more CPU)
--help                   Show this help

Debug mode flags: d = Log frames decoded with errors
                  D = Log frames decoded with zero errors
                  c = Log frames with bad CRC
                  C = Log frames with good CRC
                  p = Log frames with bad preamble
                  n = Log network debugging info
                  j = Log frames to frames.js, loadable by debug.html

FR24 Feeder/Decoderのインストール

Flightradar24 Feeder/Decoder
FR24 Feeder Settings

参考: Share data with Flightradar24
インストールスクリプトの中で必要な情報を入力する必要があるので先に準備。

  • メールアドレス 例: abc@gmail.com
  • sharingkey(新規の場合は必要なし)
  • アンテナの緯度 例: 35.xxxx
  • アンテナの経度 例: 139.xxxx
  • アンテナの海抜 例: 海抜+設置高さ=xx (単位feet)

インストールスクリプト

 $ sudo bash -c "$(wget -O - http://repo.feed.flightradar24.com/install_fr24_rpi.sh)"

赤字が入力

[main][i]FR24 Feeder/Decoder
[main][i]Version: 1.0.18-5/generic
[main][i]Built on Mar  4 2016 15:21:10 (HEAD-d11ca48.git/Linux/armv7l)
[main][i]Copyright 2012-2016 Flightradar24 AB
[main][i]http://flightradar24.com
[main][i]DNS mode: LIBC

Welcome to the FR24 Decoder/Feeder sign up wizard!

Before you continue please make sure that:

 1 - Your ADS-B receiver is connected to this computer or is accessible over network
 2 - You know your antenna's latitude/longitude up to 4 decimal points and the altitude in feet
 3 - You have a working email address that will be used to contact you
 4 - fr24feed service is stopped. If not, please run: sudo service fr24feed stop

To terminate - press Ctrl+C at any point


Step 1.1 - Enter your email address (username@domain.tld)
$:メールアドレス

Step 1.2 - If you used to feed FR24 with ADS-B data before enter your sharing key.
If you don't remember your sharing key, pelase use the retrival form:
http://feed.flightradar24.com/forgotten_key.php

Otherwise leave this field empty and continue.
$:既得のsharingkey(新規の場合は空エンター)

Step 1.3 - Would you like to participate in MLAT calculations? (yes/no)$:yes

IMPORTANT: For MLAT calculations the antenna's location should be entered very precise!

Step 3.A - Enter antenna's latitude (DD.DDDD)
$:35.xxxx

Step 3.B - Enter antenna's longitude (DDD.DDDD)
$:139.xxxx

Step 3.C - Enter antenna's altitude above the sea level (in feet)
$:xx

Using latitude: 35.xxxx, longitude: 139.xxxx, altitude: xxft above sea level

We have detected that you already have a dump1090 instance running.
We can therefore automatically configure the FR24 feeder to use existing receiver configuration,
or you can manually configure all the parameters.

Would you like to use autoconfig (*yes*/no)$:yes

Step 6A - Please select desired logfile mode:
 0 -  Disabled
 1 -  48 hour, 24h rotation
 2 -  72 hour, 24h rotation
Select logfile mode (0-2)$:1

Step 6B - Please enter desired logfile path (/var/log):
$:空エンター

Submitting form data...OK

Congratulations! You are now registered and ready to share ADS-D data with Flightradar24.
+ Your radar id is T-RJTTxxx, please include it in all email communication with us.
+ Please make sure to start sharing data within the next 3 days as otherwise your ID/KEY will be deleted.

Thank you for supporting Flightradar24!
We hope that you will enjoy our Premium services that will be available to you when you become an active feeder.

To start sending data now please execute:
sudo service fr24feed start

Saving settings to /etc/fr24feed.ini...OK
Settings saved, please run "sudo service fr24feed restart" to use new configuration.
Installation and configuration completed!

サービス再起動

$ sudo service fr24feed restart

再起動

$ sudo reboot

動作確認

RaspberryPiの場合 http://127.0.0.1:8754
他のPCの場合 http://192.168.1.184:8754

Flightradar24 Premiumに登録

flightradar24 PREMIUM Stats

フィードデータを供給するお礼にプレミアムアカウントを無料でくれるようです。登録してみます。

https://www.flightradar24.com/premium/

Flightradar24のサイトが更新されました。以下手順に近い登録方法と思います。

Get Premiumボタンでアカウント作成
* Email…FR24 Feeder/Decoder設定時に登録したメールアドレス
* Country…Japan
* Password…適当に
確認メールが届くのでリンクをクリックしログインして完了。

FR24Premiumというアプリも無料でくれるようです。
iPhone/iPad/Android/WindowsPhone/Windows8対応だそうです。

FlightAwareにフィード

FlightAware

参考: PiAware
「FlightAwareのコミュニティーに貢献していただいた御礼として、データを共有してくださるユーザー様にはEnterprise Account(USD89.95/月)を無料で提供させていただきます。」だそうです。

インストール中にユーザ名とパスワード入力が必要なので先にアカウントを作成します。

http://ja.flightaware.com/account/join/

アクティベーションメールが届くのでクリックで完了。
インストール

$ wget http://ja.flightaware.com/adsb/piaware/files/piaware_2.1-5_armhf.deb
$ sudo dpkg -i piaware_2.1-5_armhf.deb
  (エラーが出るけど次のステップで解決するから気にしない!だそうです)
$ sudo apt-get install -fy
$ sudo piaware-config -autoUpdate 1 -manualUpdate 1

<username>の部分に登録したユーザー名

$ sudo piaware-config -user <username> -password
  (パスワードの入力)
$ sudo /etc/init.d/piaware restart

MLATに関する設定

$ sudo piaware-config -mlat 1
$ sudo piaware-config -mlatResults 1
$ sudo piaware-config -mlatResultsFormat "beast,connect,localhost:30104 basestation,listen,31003"

# Connect to localhost:30104 and send multilateration results in Beast format.
# Listen on port 310003 and provide multilateration results in Basestation format to anyone who connects

確認

$ sudo piaware-config -show

再起動

$ sudo reboot

しばらくするとFlightAwareからWelcome! PiAwareのメールが届き自分の統計が確認できます。
<username>の部分に登録したユーザー名

https://flightaware.com/adsb/stats/user/<username>

MLATが有効の場合、近所の数台のレシーバーと同期し測量しますので観測地の高度と緯度経度の設定はしておいたほうがいいと思います。

PlaneFinderにフィード

PlaneFinderClient
PlaneFinderClient

参考: Plane Finder Sharing
何くれるか探してみましたが無いようです。3DViewよさげです。

最新版のバージョン確認

https://planefinder.net/sharing/client

Linux ARMHF (Raspberry Pi, BeagleBone, Radarcape etc.)のDebian Package(.deb)のURL

インストール バージョンは適宜変更

$ wget http://client.planefinder.net/pfclient_x.x.xx_armhf.deb
$ sudo dpkg -i pfclient_x.x.xx_armhf.deb

ブラウザで設定

RaspberryPiの場合 http://127.0.0.1:30053
他のPCの場合 http://192.168.1.184:30053
  • メールアドレス 例: abc@gmail.com
  • アンテナの緯度 例: 35.xxxx
  • アンテナの経度 例: 139.xxxx

Create a new sharecode (メールにsharecodeが届きます。)

  • Receiver data format: Beast
  • IP address: 127.0.0.1
  • Port: 30005

Complete configuration

PlaneFinderClient

RaspberryPiの場合 http://127.0.0.1:30053
他のPCの場合 http://192.168.1.184:30053

メモ 最新版更新方法

$ sudo service pfclient stop
$ sudo apt-get remove pfclient
$ wget http://client.planefinder.net/pfclient_x.x.xx_armhf.deb
$ sudo dpkg -i pfclient_x.x.xx_armhf.deb

メモ 参考: dump1090 ADS-B integration with FlightAware

Plane Finder ClientとDUMP1090のGooglemapプロットを比較するとPlane Finder Client側で表示しない航空機があります。
全てMLATに関わる航空機であることが分かります。
piaware-configは30104(30004)ポートにつないでMLATの結果をBeast formatに送るとなってますでの30005ポート(Beast)で使用するのが正しい気がしますが分かりません。

Virtual Radar Serverのインストール

Virtual Radar Server
VRS Web Admin

参考: Virtual Radar Server
DUMP1090より機能が豊富でよさげです。

Version 2.3.1をPaspberryPiにインストールすると設定画面で落ちる現象があり回避策を採用しています。
参考: Options Page Crashes VRS on Raspberry Pi

Linuxインストールに関する作者さんの説明

http://www.virtualradarserver.co.uk/Mono.aspx

Monoのインストール

$ sudo apt-get install mono-complete

ダウンロード

$ mkdir VirtualRadar
$ cd VirtualRadar
$ wget http://www.virtualradarserver.co.uk/Files/VirtualRadar.tar.gz
$ tar -zxvf VirtualRadar.tar.gz
$ wget http://www.virtualradarserver.co.uk/Files/Preview/VirtualRadar.tar.gz
$ tar -zxvf VirtualRadar.tar.gz.1
$ wget http://www.virtualradarserver.co.uk/Files/Preview/VirtualRadar.WebAdminPlugin.tar.gz
$ tar -zxvf VirtualRadar.WebAdminPlugin.tar.gz

一度起動(/home/pi/.local/share/VirtualRadarディレクトリ作成のため)

$ mono VirtualRadar.exe

Virtual Radar Server終了

ポート番号の変更

$ sudo leafpad /home/pi/.local/share/VirtualRadar/InstallerConfiguration.xml

コピー&ペースト (この場合は8081番に変更)

<?xml version="1.0" encoding="utf-8" ?>
<InstallerSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <WebServerPort>8081</WebServerPort>
</InstallerSettings>

Virtual Radar Server起動(ユーザー登録のための起動)

<name>に適当な名前、<password>に適当なパスワード
$ mono VirtualRadar.exe -createAdmin:<name> -password:<password>

Virtual Radar Server終了

Virtual Radar Serverに登録するDatabase、Flags、SilhouettesはKinetic Avionic公式からダウンロードすることになってますがリンク切れしてます

ネットから適当に探してきた物を登録します。

$ cd ~
$ wget http://x264.nl/dump/SBS-resources-2015-04-18-zip-date.zip
バックアップ $ wget http://dz.plala.jp/wiki_data/SBS-resources-2015-04-18-zip-date.zip
$ unzip SBS-resources-2015-04-18-zip-date.zip
$ cd VirtualRadar
$ mkdir data
$ cd data
$ mkdir Flags
$ mkdir Silhouettes
$ mkdir Pictures
$ cd ~
$ sudo mv SBS-resources/BaseStation.sqb /home/pi/VirtualRadar/data/
$ sudo cp -R SBS-resources/Files/SQBOpFlags/. /home/pi/VirtualRadar/data/Flags/
$ sudo cp -R SBS-resources/Files/SilhouettesLogos/. /home/pi/VirtualRadar/data/Silhouettes/

Virtual Radar Server起動

$ cd VirtualRadar
$ mono VirtualRadar.exe

VRS Web Admin Optionsに登録

PaspberryPiの場合 http://127.0.0.1:8081/VirtualRadar/WebAdmin/Index.html
他のPCの場合 http://192.168.1.184:8081/VirtualRadar/WebAdmin/Index.html
登録した名前、パスワードでログイン

Database filename: /home/pi/VirtualRadar/data/BaseStation.sqb
Flags folder: /home/pi/VirtualRadar/data/Flags
Silhouettes folder: /home/pi/VirtualRadar/data/Silhouettes
Pictures folder: /home/pi/VirtualRadar/data/Pictures
下のほうのSaveボタン

Virtual Radar Server終了
自動起動設定 mono-serviceでサービスに登録したかったのですが成功しないので簡易的な設定です。

$ sudo leafpad /etc/rc.local

末尾のexit 0手前に記入

mono /home/pi/VirtualRadar/VirtualRadar.exe -workingFolder:/home/pi/.local/share/VirtualRadar -noGUI

再起動

$ sudo reboot

動作確認

PaspberryPiの場合
VRS Web Admin http://127.0.0.1:8081/VirtualRadar/WebAdmin/Index.html
Default Version http://127.0.0.1:8081/VirtualRadar
Desktop Version http://127.0.0.1:8081/VirtualRadar/desktop.html
Mobile Version http://127.0.0.1:8081/VirtualRadar/mobile.html
Old Desktop Version http://127.0.0.1:8081/VirtualRadar/GoogleMap.htm
Old Mobile Version http://127.0.0.1:8081/VirtualRadar/iPhoneMap.htm
Setting Page http://127.0.0.1:8081/VirtualRadar/settings.html
他のPCの場合
VRS Web Admin http://192.168.1.184:8081/VirtualRadar/WebAdmin/Index.html
Default Version http://192.168.1.184:8081/VirtualRadar
Desktop Version http://192.168.1.184:8081/VirtualRadar/desktop.html
Mobile Version http://192.168.1.184:8081/VirtualRadar/mobile.html
Old Desktop Version http://192.168.1.184:8081/VirtualRadar/GoogleMap.htm
Old Mobile Version http://192.168.1.184:8081/VirtualRadar/iPhoneMap.htm
Setting Page http://192.168.1.184:8081/VirtualRadar/settings.html

メモ

デフォルトで設定されるのがBaseStationフォーマットの30003ポートです。
Beast Raw Feedの30005ポートでも動きます。
どちらがいいのか分かりません。

ModeSMixer2のインストール

ModeSMixer2
ModeSMixer2
ModeSMixer2
ModeSMixer2

参考: xdeco.org
受信エリアを表示してくれて他にはない機能です。よさげです。
ダウンロード

wgetでダウンロードできないのでRaspberryPiのブラウザから
http://xdeco.org/?page_id=30#mm2
Raspberry Pi用が数種類用意されてるので環境にあったものをダウンロード(googledriveでは↓マーク)

ブラウザの設定によるが/home/pi/Downloads辺りにダウンロードされてる

$ mkdir modesmixer2
$ sudo mv Downloads/modesmixer2_rpi2_20160119.tgz /home/pi/modesmixer2
$ cd modesmixer2
$ tar -zxvf modesmixer2_rpi2_20160119.tgz

DatabaseとSilhouettesとFlightRouteを登録できるようです。
DatabaseとSilhouettesはVirtual Radar Serverで登録したものをコピーし、
FlightRouteは用意の仕方が分からないので「まんしゅう彩遊記」さんから頂きます。
DatabaseとSilhouettes

$ sudo cp -R /home/pi/VirtualRadar/data/. /home/pi/modesmixer2/

FlightRoute

RaspberryPiのブラウザから
http://newmansyuu.blog.fc2.com/blog-entry-433.html
キャビネットボード
http://acarsman.in.coocan.jp/cab/viewtopic.php?f=2&t=177
flightroute_w.zip (62,993 byte) 15/09/11 更新  ダウンロード
ブラウザの設定によるが/home/pi/Downloads辺りにダウンロードされてる
$ cd ~
$ sudo mv /home/pi/Downloads/flightroute_w.zip /home/pi/modesmixer2
$ cd modesmixer2
$ unzip flightroute_w.zip

自動起動設定

$ sudo leafpad /etc/init.d/modesmixer2

コピーペースト
--location 35.xxxx:139.xxxxは観測地の緯度経度

#! /bin/sh
### BEGIN INIT INFO
# Provides:          modesmixer2
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: modesmixer2
# Description:       modesmixer2
### END INIT INFO

# Author: Foo Bar <foobar@baz.org>
#
# Please remove the "Author" lines above and replace them
# with your own name if you copy and modify this script.

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="modesmixer2"
NAME=modesmixer2
DAEMON=/home/pi/modesmixer2/$NAME
DAEMON_ARGS="--inConnect 127.0.0.1:30005 --location 35.xxxx:139.xxxx --web 8082 --db /home/pi/modesmixer2/BaseStation.sqb --frdb /home/pi/modesmixer2/flightroute_w.sqb --silhouettes /home/pi/modesmixer2/Silhouettes"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon --start --background --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
                || return 1
        start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --exec $DAEMON -- $DAEMON_ARGS \
                || return 2
        # Add code here, if necessary, that waits for the process to be ready
        # to handle requests from services started subsequently which depend
        # on this one.  As a last resort, sleep for some time.
}

#
# Function that stops the daemon/service
#
do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
        RETVAL="$?"
        [ "$RETVAL" = 2 ] && return 2
        # Wait for children to finish too if this is a daemon that forks
        # and if the daemon is only ever run from this initscript.
        # If the above conditions are not satisfied then add some other code
        # that waits for the process to drop all resources that could be
        # needed by services started subsequently.  A last resort is to
        # sleep for some time.
        start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
        [ "$?" = 2 ] && return 2
        # Many daemons don't delete their pidfiles when they exit.
        rm -f $PIDFILE
        return "$RETVAL"
}

#
# main
#
case "$1" in
  start)
        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  stop)
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  #status)
        #status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
        #;;
  #reload|force-reload)
        #
        # If do_reload() is not implemented then leave this commented out
        # and leave 'force-reload' as an alias for 'restart'.
        #
        #log_daemon_msg "Reloading $DESC" "$NAME"
        #do_reload
        #log_end_msg $?
        #;;
  restart|force-reload)
        #
        # If the "reload" option is implemented then remove the
        # 'force-reload' alias
        #
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  *)
        #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
        echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
        exit 3
        ;;
esac
:

権限変更

$ sudo chmod 755 /etc/init.d/modesmixer2

サービス登録

$ sudo update-rc.d modesmixer2 defaults

再起動

$ sudo reboot

動作確認

RaspberryPiの場合 http://127.0.0.1:8082
他のPCの場合 http://192.168.1.184:8082

Airspy ADSB Hubにフィード

Airspy ADSB Hub

参考: ADSB | airspy.com
ModeSMixer2を入れると簡単にフィードできます。

modesmixer2の起動scriptを少し変えるだけです。

 $ sudo leafpad /etc/init.d/modesmixer2

--outConnect avr:sdrsharp.com:47806を追加するだけ
--location 35.xxxx:139.xxxxは観測地の緯度経度

DAEMON_ARGS="--inConnect 127.0.0.1:30005 --outConnect avr:sdrsharp.com:47806 --location 35.xxxx:139.xxxx --web 8082 --db /home/pi/modesmixer2/BaseStation.sqb --frdb /home/pi/modesmixer2/flightroute_w.sqb --silhouettes /home/pi/modesmixer2/Silhouettes"

再起動

 $ sudo reboot

動作確認
ModeSMixer2経由なのでModeSMixer2のLogで確認できます。
outConnect(avr:sdrsharp.com:47806) connectedになってれば成功してると思います。

RaspberryPiの場合 http://127.0.0.1:8082
他のPCの場合 http://192.168.1.184:8082

確認

http://sdrsharp.com:8080/virtualradar/

たくさんフィードされててちゃんと表示してるか確認できません(;゚Д゚)。

ADS-B Exchangeにフィード

ADS-B Exchange

参考: ADS-B Exchange
これもModeSMixer2で簡単にフィードできるはずなんですが失敗してしまいます。
原因わかりません。

× --outConnect beast:feed.adsbexchange.com:30005
× --outConnect avr:feed.adsbexchange.com:30005
× --outConnect avrmlat:feed.adsbexchange.com:30005

Virtual Radar ServerのRebroadcastの設定方法も紹介されてるので試してみます。

http://www.adsbexchange.com/how-to-feed-from-vrs/

こんな感じで登録して×ボタンで画面下のSaveボタン
FR24 adsbexchange.jpg

再起動

$ sudo reboot

動作確認
VRS Web AdminのRebroadcast serversでカウントされてれば成功してると思います。

PaspberryPiの場合
VRS Web Admin http://127.0.0.1:8081/VirtualRadar/WebAdmin/Index.html
他のPCの場合
VRS Web Admin http://192.168.1.184:8081/VirtualRadar/WebAdmin/Index.html

確認

http://www.adsbexchange.com/ のGlobal Radar View(直リンクできないようです)

ModeSDeco2のインストール

参考: xdeco.org

dump1090-mutabilityと同時に起動するとDVB-T USBを2箇所から参照しようとして接続に失敗します。
簡単な切り替えで使用できないものか検討中です。
以下設定しても動きません。

ダウンロード

wgetでダウンロードできないのでRaspberryPiのブラウザから
http://xdeco.org/
Raspberry Pi 2用をダウンロード(googledriveでは↓マーク)

ブラウザの設定によるが/home/pi/Downloads辺りにダウンロードされてる

$ mkdir modesdeco2
$ sudo mv Downloads/modesdeco2_rpi2_20150815.tgz /home/pi/modesdeco2
$ cd modesdeco2
$ tar -zxvf modesdeco2_rpi2_20150815.tgz

起動

locationは観測地の緯度経度
$ ./modesdeco2 --location 35.xxxx:139.xxxx --web 8084

動作確認

RaspberryPiの場合 http://127.0.0.1:8084
他のPCの場合 http://192.168.1.184:8084

nginxでCORS対策

JSON

DUMP1090はCORS(Cross-Origin Resource Sharing)対策されていません。
Raspberry Pi上で動くDUMP1090webサーバ機能の中からJSON(これは受信電波をデコードしてテキスト化したもの)をローカルネットまたはWAN側の他のサーバまたはアプリケーションから参照しようとするとCORSに該当してしまい利用できない状態に陥ります。
ここではnginxのproxyでCORSヘッダーを加えて対策してみます。
インストール

$ sudo apt-get install nginx

新規作成

$ sudo leafpad /etc/nginx/sites-available/proxy

コピーペースト(この場合は49153番ポートの接続は全て自身の8080番ポート=DUMP1090につなぎCORS対策ヘッダーを加えます。)
こんな変なポート使用してすいません。WANからの接続をルーター側で49153番にしてしまいました。

server {
    listen 49153;
    server_name localhost;
    location / {
       add_header Access-Control-Allow-Origin *;
       add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
       add_header Access-Control-Allow-Headers "Origin, Authorization, Accept";
       add_header Access-Control-Allow-Credentials true;
       proxy_pass http://localhost:8080/;
    }
}

シンボリックリンク(defaultに80番ポートの記述があり使用しないので無効に)

$ sudo rm /etc/nginx/sites-enabled/default
$ sudo ln -s /etc/nginx/sites-available/proxy /etc/nginx/sites-enabled/proxy

サービス再起動

$ sudo service nginx restart

ローカルネット内の他のパソコンから接続してみる

http://192.168.1.184:49153/data/aircraft.json
※なぜかブラウザに直接キー入力しないと開かないブラウザがあります。alias未設定が問題のように感じます。

MyRadar24

MyRadar24

nginxでCORS対策で読み取り加工が可能になったJSONデータを別サーバのwebアプリケーションから参照してリアルタイムプロットしてみます。

MyRadar24

ほとんどのアプリが地図ベースのwebアプリまたは統計なので、nginxのproxy経由であればほぼ全てのアプリから欲しい機能を取り込みオリジナルのアプリケーションは比較的簡単にできてしまうと思います。

ClamTkのインストール

負荷が高く使用しないほうが安定するように思います。

nginxを外部に公開するとウィルスが気になります。
ウイルススキャンソフトClamAVのGUIフロントエンドClamTkをインストールしてみます。

 $ sudo apt-get install clamtk

Menu→アクセサリ→ClamTKで起動

dump1090-toolsのインストール

Dump1090 Graphs

参考: dump1090-tools for raspberry pi.
便利そうなので入れてみようと思います。
webサーバ機能は無いのでnginxを使用してみます。
8083番ポートで動くようにしてみます。
新規作成

$ sudo leafpad /etc/nginx/sites-available/dump1090-tools

コピーペースト

server {
    listen 8083;
    root /var/www/collectd;
    index index.html index.htm index.php;
    server_name localhost;
    location / {
       
    }
}

シンボリックリンク

$ sudo ln -s /etc/nginx/sites-available/dump1090-tools /etc/nginx/sites-enabled/dump1090-tools

サービス再起動

$ sudo service nginx restart

dump1090-toolsのインストール

$ cd /tmp
$ wget https://raw.githubusercontent.com/tedsluis/dump1090-tools/master/dump1090-tools-install.sh
$ chmod +x dump1090-tools-install.sh
$ ./dump1090-tools-install.sh
エラーが出ますが気にしない
$ sudo service collectd stop

書き換え
http://localhost/dump1090/data/stats.jsonが読み込める位置で設定されてるようです。
nginxのCORS対策でhttp://localhost:49153/data/stats.jsonに変更してますので書き換えが必要です。
参考: Web Portal and Collectd/rrd graphs automated installation

$ sudo leafpad /etc/collectd/collectd.conf

URLの行を書き換え

<Plugin python>
        ModulePath "/home/pi/dump-tools/collectd"
        LogTraces true
        Import "dump1090"
        <Module dump1090>
                <Instance rpi>
                        URL "http://localhost:49153"
                </Instance>
        </Module>
</Plugin>

その他設定

$ sudo service collectd start
$ ls -lR /var/lib/collectd/rrd/* | grep rrd
$ ps -ef | grep collectd
$ cd /home/pi/dump-tools/collectd/
$ sudo ./graphs-crontabjob.sh
$ ls -lR /var/www/collectd/

再起動

$ sudo reboot

動作確認
最初は欠けてる画像もありますが徐々に表示します。

PaspberryPiの場合 http://127.0.0.1:8083
他のPCの場合 http://192.168.1.184:8083

インターフェイスが見劣りしてると思います。改良してみます。

バックアップ

$ sudo mv /var/www/collectd/index.html /var/www/collectd/index.html_bak

新規作成

$ sudo leafpad /var/www/collectd/index.html

コピーペースト

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="pragma" content="no-cache">
<title>Dump1090 Graphs</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<style>
@media (min-width: 768px) {
	img {
		width: 719px;
		height: 378px;
	}
	.contents {
		margin-top:102px;
	}
}
@media (min-width: 992px) {
	img {
		width: 466px;
		height: 246px;
	}
	.contents {
		margin-top:52px;
	}
}
@media (min-width: 1200px) {
	img {
		width: 566px;
		height: 300px;
	}
	.contents {
		margin-top:52px;
	}
}
img {
	float: left;
	margin: 2px;
}
.navbar {
	position: fix;
}
</style>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script>
var base_name = "dump1090-rpi";

function switchView(time_name){
	<!-- Compose picture names //-->
	$("#cpu").attr("src", base_name + "-cpu-" + time_name + ".png");
	$("#mem").attr("src", base_name + "-memory-" + time_name + ".png");
	$("#net").attr("src", base_name +"-eth0-" + time_name + ".png");
	$("#disk").attr("src", base_name +"-disk-" + time_name + ".png");
	$("#rate").attr("src", base_name + "-rate-" + time_name + ".png");
	$("#signal").attr("src", base_name + "-signal-" + time_name + ".png");
	$("#acs").attr("src", base_name + "-acs-" + time_name + ".png");
	$("#range").attr("src", base_name + "-range-" + time_name + ".png");
	$("#tracks").attr("src", base_name + "-tracks-" + time_name + ".png");
}
</script>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
	<div class="container">
		<div class="navbar-header">
			<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button>
			<a class="navbar-brand" href="#">Dump1090 Graphs</a> </div>
		<div id="navbar" class="collapse navbar-collapse">
			<ul class="nav navbar-nav">
				<li><a href="#" onclick="event.preventDefault();switchView('1h')">Hour</a></li>
				<li><a href="#" onclick="event.preventDefault();switchView('6h')">6Hour</a></li>
				<li><a href="#" onclick="event.preventDefault();switchView('24h')">Day</a></li>
				<li><a href="#" onclick="event.preventDefault();switchView('2d')">2Day</a></li>
				<li><a href="#" onclick="event.preventDefault();switchView('3d')">3Day</a></li>
				<li><a href="#" onclick="event.preventDefault();switchView('7d')">Week</a></li>
				<li><a href="#" onclick="event.preventDefault();switchView('14d')">2Week</a></li>
				<li><a href="#" onclick="event.preventDefault();switchView('30d')">Month</a></li>
				<li><a href="#" onclick="event.preventDefault();switchView('90d')">Quarter</a></li>
				<li><a href="#" onclick="event.preventDefault();switchView('365d')">Year</a></li>
			</ul>
		</div>
	</div>
</nav>
<div class="container contents">
	<img id="cpu" src="dump1090-rpi-cpu-24h.png">
	<img id="mem" src="dump1090-rpi-memory-24h.png">
	<img id="net" src="dump1090-rpi-eth0-24h.png">
	<img id="disk" src="dump1090-rpi-disk-24h.png">
	<img id="rate" src="dump1090-rpi-rate-24h.png">
	<img id="signal" src="dump1090-rpi-signal-24h.png">
	<img id="acs" src="dump1090-rpi-acs-24h.png">
	<img id="range" src="dump1090-rpi-range-24h.png">
	<img id="tracks" src="dump1090-rpi-tracks-24h.png">
</div>
</body>
</html>

動作確認

PaspberryPiの場合 http://127.0.0.1:8083
他のPCの場合 http://192.168.1.184:8083

dump1090 maximum rangeの設定

dump1090-mutability with maximum range

参考:引用: What is the Maximum Range I can Get?

チュートリアルの大雑把な翻訳

イントロダクション
どんな位置でも達成可能な最大範囲を期待します。
(1) 地球の湾曲。
(2) その位置のまわりの地形。丘と標高の変化が最大範囲を制限し、地形は非常に重要な役割を果たしています。

これは、ギガヘルツ/マイクロ波帯の電波の伝播が視線ラインであるという事実に起因しています。従って、範囲は地球の湾曲により制限されて、完全に水平地形の理想的な条件では約250海里/450kmです。丘と標高の上昇はさらに最大範囲を250海里/450km未満に制限します。

あなたの最大可能範囲を見つけてください
あなたの場所で得ることができる最大可能範囲を決定するために、以下の手順に従ってください:
(1) サイトに行ってください。 http://www.heywhatsthat.com
(2) "new panorama"というタブを選んでください。
(3) あなたの緯度と経度を入力してください。
(4) あなたの高さを入力してください。(=あなたのアンテナの高さの入力)
(5) タイトルを入力してください。
(6) "submit request"ボタンを押してください。
(7) パノラマが生成される間、スポンサーの広告見て待ってください。
(8) パノラマが生成されたら地図にスクロールして、地図の右上にあるタブ"up in the air"をクリックします。
(9) 青と黄色の2つの円曲線が見えるまで地図を縮小、10,000フィートと30,000フィートの高さで航空機の最大距離を示します。
(10) 地図の下のテキストボックスにデフォルトの航空機の高さ10,000フィートと30,000フィートが黄色と水色で点灯表示されます。あなたの要件に合うよう変更し、"Enter"ボタンを押してください。2つの曲線はあなたが入力した新しい高さに変更されます。

20986467722 4bb2443258 z.jpg

電波の大気屈折および視線ラインの無線の層は50から100海里視線ライン超えて延びていてもよいです。最大可能範囲はあなたがheywhatsthat.comサイトから得た曲線によって示される最大可能範囲よりも約50〜100海里大きくなります。

20296900424 fe935dd99a o.png

最大可能範囲を達成するために、あなたのアンテナは木やそれを囲む家の上の高さに設置する必要があります。そして地平線が見える必要があります。

チュートリアル通り進めると欲しい円曲線が表示できます。
dump1090-mutabilityにはheywhatsthat.comの円曲線データ(upintheair.json)を表示する機能があらかじめ用意されています。
この自身で作成したデータを登録することで機能します。

JSONデータの登録 <myID>と<range>は適宜入力

$ sudo wget -O /usr/share/dump1090-mutability/html/upintheair.json "http://www.heywhatsthat.com/api/upintheair.json?id=<myID>&refraction=0.25&alts=<range>"

<myID>

2015-11-15 19-28-49.png

<range>
フィートをメートル変換した数値で設定します。
3048 m = 10,000 feet, 6096m = 20,000 feet, 9144 m = 30,000 feet, 12192 m = 40,000 feet
複数登録する場合は3048,6096,9144,12192のように「,」区切りです。

動作確認

RaspberryPiの場合 http://127.0.0.1:8080
他のPCの場合 http://192.168.1.184:8080
※なぜかブラウザに直接キー入力しないと開かないブラウザがあります。dump1090のaliasが未設定のように感じます。

メモ 円曲線の色を変えたい場合はscript.jsを書き換えます。
script.jsの510行目辺り

        request.done(function(data) {
                for (var i = 0; i < data.rings.length; ++i) {
                        
                        var limitColor;
                        if (i==0){limitColor='#db5820'};
                        if (i==1){limitColor='#d0ea16'};
                        if (i==2){limitColor='#258fe5'};
                        if (i==3){limitColor='#1f10dd'};
                        
                        var points = data.rings[i].points;
                        var ring = [];
                        for (var j = 0; j < points.length; ++j) {
                                ring.push(new google.maps.LatLng(points[j][0], points[j][1]));
                        }
                        ring.push(ring[0]);

                        new google.maps.Polyline({
                                path: ring,
                                strokeOpacity: 1.0,
                                //strokeColor: '#000000',
                                strokeColor: limitColor,
                                strokeWeight: 2,
                                map: GoogleMap });
                }
        });

dump1090 gainの調整

参考: Gain Adjustment...
気持ち安定化させるため最初に再起動

$ sudo reboot

dump1090-mutabilityの一時停止

$ sudo service dump1090-mutability stop

pythonスクリプト作成

$ sudo leafpad optimize-gain.py

コピーペースト

#!/usr/bin/python2
import time, socket, subprocess

measure_duration = 15 #seconds
ntests = 10
#gains = "20.7 22.9 25.4 28.0 29.7 32.8 33.8 36.4 37.2 38.6 40.2 42.1 43.4 43.9 44.5 48.0 49.6".split()
gains = "36.4 38.6 40.2 42.1 44.5 48.0 49.6".split()
#gains = "22.9 25.4 28.0 29.7 32.8 33.8 36.4".split()
gains.reverse()
results = {}

for i in range(ntests):
  print "test", i+1, "of", ntests
  for g in gains:
   if g not in results:
      results[g] = [0,0,{}] #msgs, positions, aircraft
   p = subprocess.Popen(('/usr/bin/dump1090-mutability --net --gain '+g+' --oversample --fix --phase-enhance --quiet').split(),shell=False,
    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   time.sleep(2)
   s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   s.connect(('localhost',30003))
   t = time.time()
   d = ''
   while 1:
      d += s.recv(32)
      if time.time() - t > measure_duration:
         break
   s.close()
   p.terminate()
   messages = 0
   positions = 0
   planes = {}
   for l in d.split('\n'):
      a = l.split(',')
      messages += 1
      if len(a) > 4:
         if a[1] == '3':
            positions += 1
         planes[a[4]] = 1
   print "gain=",g, "messages=", messages, "positions=", positions, "planes=", len(planes.keys())
   results[g][0] += messages
   results[g][1] += positions
   for hex in planes.keys():
      results[g][2][hex] = 1

print "\n===Totals==="
print "Gain, Messages, Positions, Aircraft"
for g in gains:
   (messages,positions,planes) = results[g]
   print g, messages, positions, len(planes.keys())

権限変更

$ sudo chmod +x optimize-gain.py

実行

$ ./optimize-gain.py
test 1 of 10
gain= 49.6 messages= 778 positions= 116 planes= 12
gain= 48.0 messages= 898 positions= 123 planes= 12
gain= 44.5 messages= 912 positions= 139 planes= 10
gain= 42.1 messages= 915 positions= 140 planes= 12
gain= 40.2 messages= 770 positions= 106 planes= 10
gain= 38.6 messages= 656 positions= 95 planes= 9
gain= 36.4 messages= 644 positions= 87 planes= 7
test 2 of 10
gain= 49.6 messages= 829 positions= 85 planes= 12
gain= 48.0 messages= 708 positions= 94 planes= 13
gain= 44.5 messages= 728 positions= 90 planes= 13
gain= 42.1 messages= 866 positions= 99 planes= 13
gain= 40.2 messages= 667 positions= 75 planes= 13
gain= 38.6 messages= 440 positions= 54 planes= 10
gain= 36.4 messages= 298 positions= 32 planes= 7
test 3 of 10
gain= 49.6 messages= 1034 positions= 114 planes= 19
gain= 48.0 messages= 1150 positions= 124 planes= 16
gain= 44.5 messages= 919 positions= 92 planes= 15
gain= 42.1 messages= 787 positions= 102 planes= 12
gain= 40.2 messages= 541 positions= 82 planes= 12
gain= 38.6 messages= 603 positions= 70 planes= 10
gain= 36.4 messages= 503 positions= 73 planes= 10
test 4 of 10
gain= 49.6 messages= 772 positions= 108 planes= 13
gain= 48.0 messages= 730 positions= 98 planes= 10
gain= 44.5 messages= 744 positions= 84 planes= 12
gain= 42.1 messages= 640 positions= 89 planes= 11
gain= 40.2 messages= 692 positions= 85 planes= 10
gain= 38.6 messages= 641 positions= 91 planes= 9
gain= 36.4 messages= 588 positions= 87 planes= 8
test 5 of 10
gain= 49.6 messages= 949 positions= 132 planes= 14
gain= 48.0 messages= 931 positions= 127 planes= 13
gain= 44.5 messages= 978 positions= 147 planes= 12
gain= 42.1 messages= 1020 positions= 153 planes= 14
gain= 40.2 messages= 733 positions= 115 planes= 11
gain= 38.6 messages= 531 positions= 78 planes= 10
gain= 36.4 messages= 427 positions= 60 planes= 7
test 6 of 10
gain= 49.6 messages= 747 positions= 111 planes= 14
gain= 48.0 messages= 690 positions= 105 planes= 13
gain= 44.5 messages= 701 positions= 101 planes= 11
gain= 42.1 messages= 699 positions= 89 planes= 9
gain= 40.2 messages= 672 positions= 80 planes= 9
gain= 38.6 messages= 540 positions= 58 planes= 10
gain= 36.4 messages= 536 positions= 51 planes= 9
test 7 of 10
gain= 49.6 messages= 602 positions= 78 planes= 13
gain= 48.0 messages= 509 positions= 60 planes= 13
gain= 44.5 messages= 331 positions= 43 planes= 8
gain= 42.1 messages= 367 positions= 45 planes= 10
gain= 40.2 messages= 310 positions= 39 planes= 9
gain= 38.6 messages= 499 positions= 40 planes= 9
gain= 36.4 messages= 466 positions= 45 planes= 6
test 8 of 10
gain= 49.6 messages= 585 positions= 47 planes= 14
gain= 48.0 messages= 805 positions= 68 planes= 15
gain= 44.5 messages= 779 positions= 74 planes= 13
gain= 42.1 messages= 888 positions= 75 planes= 14
gain= 40.2 messages= 771 positions= 65 planes= 11
gain= 38.6 messages= 574 positions= 50 planes= 7
gain= 36.4 messages= 512 positions= 30 planes= 7
test 9 of 10
gain= 49.6 messages= 835 positions= 46 planes= 14
gain= 48.0 messages= 837 positions= 58 planes= 10
gain= 44.5 messages= 796 positions= 67 planes= 9
gain= 42.1 messages= 630 positions= 50 planes= 8
gain= 40.2 messages= 756 positions= 33 planes= 11
gain= 38.6 messages= 936 positions= 73 planes= 12
gain= 36.4 messages= 967 positions= 80 planes= 9
test 10 of 10
gain= 49.6 messages= 975 positions= 111 planes= 16
gain= 48.0 messages= 938 positions= 103 planes= 18
gain= 44.5 messages= 778 positions= 88 planes= 14
gain= 42.1 messages= 717 positions= 107 planes= 10
gain= 40.2 messages= 691 positions= 81 planes= 10
gain= 38.6 messages= 499 positions= 55 planes= 8
gain= 36.4 messages= 270 positions= 22 planes= 6

===Totals===
Gain, Messages, Positions, Aircraft
49.6 8106 948 52
48.0 8196 960 50
44.5 7666 925 45
42.1 7529 949 44
40.2 6603 761 41
38.6 5919 664 40
36.4 5211 567 34

Aircraftの欄を見るんでしょうか?49.6がいちばん感度がいいようです。

dump1090-mutabilityコンフィグの書き換え

$ sudo leafpad /etc/default/dump1090-mutability

書き換え箇所

#GAIN="max"
GAIN="49.6"

再起動

$ sudo reboot

flightdataの設定

参考: <Stuff about="code" />: Read PiAware Flight Data with Python

dump1090-mutabilityの生ストリームは利用できるデータにするまであまりにも多くの仕事をさせているように見えます。JSONデータを使用するほうが簡単に感じます。<Stuff about="code" />さんがモジュール向けpythonスクリプトを公開してくれてます。このままでは動かないので若干修正してみます。

ソースのダウンロード

$ git clone https://github.com/martinohanlon/flightdata.git
$ cd flightdata

修正

$ sudo leafpad flightdata.py

コピーペースト

# coding:utf-8

from urllib2 import urlopen
import json
from time import sleep
#import datetime

#DUMP1090DATAURL = "http://localhost:8080/data.json"
DUMP1090DATAURL = "http://localhost:8080/data/aircraft.json"

class FlightData():
    def __init__(self, data_url = DUMP1090DATAURL):

        self.data_url = data_url

        self.refresh()

    def refresh(self):
        #open the data url
        self.req = urlopen(self.data_url)

        #read data from the url
        self.raw_data = self.req.read()

        #charset the data
        #charset = self.req.headers.get_content_charset()
        charset = self.req.headers.getparam("charset")

        #load in the json
        #self.json_data = json.loads(self.raw_data.decode(encoding))
        self.json_data = json.loads(self.raw_data.decode(charset))

        self.aircraft = AirCraftData.parse_flightdata_json(self.json_data)

class AirCraftData():
    def __init__(self,
                 now,
                 dhex,
                 flight,
                 lat,
                 lon,
                 altitude,
                 vert_rate,
                 track,
                 speed,
                 squawk,
                 category,
                 messages,
                 seen,
                 seen_pos,
                 nucp,
                 rssi,
                 #validposition,
                 #validtrack,
                 mlat):

        self.now = now
        self.hex = dhex
        self.flight = flight
        self.lat = lat
        self.lon = lon
        self.altitude = altitude
        self.vert_rate = vert_rate
        self.track = track
        self.speed = speed
        self.squawk = squawk
        self.category = category
        self.messages = messages
        self.seen = seen
        self.seen_pos = seen_pos
        self.nucp = nucp
        self.rssi = rssi
        #self.validposition = validposition
        #self.validtrack = validtrack
        self.mlat = mlat

    @staticmethod
    def parse_flightdata_json(json_data):
        aircraft_list = []
        nowvalue = json_data["now"] #unix time
        #now = datetime.datetime.fromtimestamp(nowvalue)
        for aircraft in json_data["aircraft"]:
            aircraftdata = AirCraftData(
                nowvalue,
                aircraft.get("hex", ""),
                aircraft.get("flight", ""),
                aircraft.get("lat", ""),
                aircraft.get("lon", ""),
                aircraft.get("altitude", ""),
                aircraft.get("vert_rate", ""),
                aircraft.get("track", ""),
                aircraft.get("speed", ""),
                aircraft.get("squawk", ""),
                aircraft.get("category", ""),
                aircraft.get("messages", ""),
                aircraft.get("seen", ""),
                aircraft.get("seen_pos", ""),
                aircraft.get("nucp", ""),
                aircraft.get("rssi", ""),
                #aircraft.get("validposition", ""),
                #aircraft.get("validtrack", ""),
                aircraft.get("mlat", ""))
            aircraft_list.append(aircraftdata)
        return aircraft_list

#test
if __name__ == "__main__":
    #create FlightData object
    myflights = FlightData()
    while True:
        #loop through each aircraft found
        for aircraft in myflights.aircraft:
            #check if the record has a position
            if (aircraft.seen_pos):
                aircraft.now = "unix_time:" + str(aircraft.now)
                aircraft.hex = "icao:" + aircraft.hex
                if (aircraft.flight):
                    aircraft.flight = "flight:" + aircraft.flight
                else:
                    aircraft.flight = "flight:	"
                if (aircraft.lat):
                    aircraft.lat = "lat:" + str(aircraft.lat) + "°"
                if (aircraft.lon):
                    aircraft.lon = "lon:" + str(aircraft.lon) + "°"
                if (aircraft.altitude):
                    aircraft.altitude = "altitude:" + str(aircraft.altitude) + "ft"
                if (aircraft.vert_rate > 0):
                    aircraft.vert_rate = "vert_rate:△"
                elif (aircraft.vert_rate < 0):
                    aircraft.vert_rate = "vert_rate:▽"
                else:
                    aircraft.vert_rate = ""
                if (aircraft.track):
                    aircraft.track = "track:" + str(aircraft.track) + "°"
                if (aircraft.speed):
                    aircraft.speed = "speed:" + str(aircraft.speed) + "kt"
                if (aircraft.squawk):
                    aircraft.squawk = "squawk:" + aircraft.squawk
                #if (aircraft.category):
                    #aircraft.category = "category:" + aircraft.category
                #if (aircraft.messages):
                    #aircraft.messages = "messages:" + str(aircraft.messages)
                #if (aircraft.seen):
                    #aircraft.seen = "seen:" + str(aircraft.seen) + "s"
                #if (aircraft.seen_pos):
                    #aircraft.seen_pos = "seen_pos:" + str(aircraft.seen_pos) + "s"
                #if (aircraft.nucp):
                    #aircraft.nucp = "nucp:" + str(aircraft.nucp)
                #if (aircraft.rssi):
                    #aircraft.rssi = "rssi:" + str(aircraft.rssi) + "dBFS"
                if (aircraft.mlat):
                    aircraft.mlat = "MLAT"

                #print the aircraft"s data
                print aircraft.now, aircraft.hex, aircraft.flight, aircraft.lat, aircraft.lon, aircraft.altitude, aircraft.vert_rate, aircraft.track, aircraft.speed, aircraft.squawk, aircraft.mlat #, aircraft.category, aircraft.messages, aircraft.seen, aircraft.seen_pos, aircraft.nucp, aircraft.rssi, aircraft.validposition, aircraft.validtrack

        sleep(1)

        #refresh the flight data
        myflights.refresh()

実行

$ python flightdata.py

文字がだらだら流れれば問題ないと思います。
Flightdata.jpg

他のpythonスクリプトでモジュールとして使用する場合

$ sudo leafpad test.py

コピーペースト

from flightdata import FlightData
from time import sleep

myflights = FlightData()
while True:
    #loop through each aircraft found
    for aircraft in myflights.aircraft:
        #print the aircraft"s data
        if (aircraft.mlat):
            aircraft.mlat = "MLAT"
        print aircraft.now, aircraft.hex, aircraft.flight, aircraft.mlat, aircraft.lat, aircraft.lon, aircraft.altitude, aircraft.vert_rate, aircraft.track, aircraft.speed, aircraft.squawk #, aircraft.category, aircraft.messages, aircraft.seen, aircraft.seen_pos, aircraft.nucp, aircraft.rssi, aircraft.validposition, aircraft.validtrack

    sleep(1)

    #refresh the flight data
    myflights.refresh()

実行

$ python test.py

flight-warningの設定

flight-warning設定例

参考: GitHub - darethehair/flight-warning: Aircraft proximity detector from dump1090 feed

設定座標から設定距離内の航空機を検出するとGmailにメール(ALERT)を送信します。
航空機の軌跡が検出ゾーンと交差する可能性がある場合は警告メール(WARNING)を送信します。

オリジナルのソースでしばらく走らせてみた感じではメモリ消費量が激しく感じます。dump1090-mutabilityのRAWデータを自前で解析してるためと思います。flightdata.pyのモジュールと連携する方向で設定してみます。

事前に「flightdataの設定」を設定しておいてください。

$ cd ~
$ git clone https://github.com/darethehair/flight-warning.git
$ cp /home/pi/flightdata/flightdata.py /home/pi/flight-warning
$ cd flight-warning

書き換え

$ sudo leafpad flight_warning.py
  • Googleアカウントの2段階認証プロセスを利用しているため、Passwordはアプリパスワードを使用して確認してます。
  • 空港が近く異常に反応してしまうので検出除外ゾーンを追加しました。
  • MLAT航空機、特にヘリコプターに異常に反応してしまうのでMLAT無効フラグ追加しました。
  • オリジナルの検出ゾーンは円柱形で上空10kmの航空機も検出してしまいます。扁平楕円体の検出ゾーンに書き換えてます。
  • 理解に苦しんだので概略作りました。

クロストラックエラールーティンは「今の侵入角度だと将来来る可能性のある最接近距離(distance3)」をチェックしています。WARNINGエリア(warning_distance~warning_oblate)で4km、ALERTエリア(alert_distance~alert_oblate)で2km以内に入ったら反応する設定にしてます。
概略.jpg

コピーペースト

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
flight_warning.py
version 1.06

This program will send a Google mail message when an ADS-B data feed from
a dump1090 stream detects an aircraft within a set distance of a geographic point.
It will also send an email when the aircraft leaves that detection area.
As well, it will send a warning email if the trajectory of the plane is likely to
intersect the detection zone.

Copyright (C) 2015 Darren Enns <darethehair@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
"""
# import required libraries
import sys
import smtplib
import datetime
import time
import math
import copy
from math import atan2, sin, cos, radians, degrees, atan
from flightdata import FlightData
from time import sleep

# flightdata.py module
myflights = FlightData()

# initialize empty dictionaries
plane_dict = {}
plane_hist = {}

# set desired units
metric_units = True # True / False

# MLAT
mlat_enable = False # True / False

# set desired distance and time limits
warning_distance = 14 # km metric_units = True / miles metric_units = False
warning_oblate = 6 # oblate spheroid (a = b, c = alert_oblate) km metric_units = True / miles metric_units = False
alert_distance = 7 # km metric_units = True / miles metric_units = False
alert_oblate = 3 # oblate spheroid (a = b, c = alert_oblate) km metric_units = True / miles metric_units = False
duplicate_minutes = 20 # minute

# set geographic location and elevation
my_lat = 35.xxxxxx
my_lon = 139.xxxxxx
my_elevation = 23 # m metric_units = True / feet metric_units = False

# option If you do not want to use, radius 0
exclusion_lat = 35.xxxxxx
exclusion_lon = 139.xxxxxx
exclusion_radius = 9 # km metric_units = True / miles metric_units = False

# set gmail userids and creditials
gmail_recv_user = 'abc.gfx@gmail.com'
gmail_send_user = 'abc.gfx@gmail.com'
gmail_pwd = 'xxxxxxxxxxxxxxxx';

# define haversine great-circle-distance routine
# credit: http://www.movable-type.co.uk/scripts/latlong.html
def haversine(origin, destination):
	lat1, lon1 = origin
	lat2, lon2 = destination
	lat3 = math.radians(lat1);
	lat4 = math.radians(lat2);
	delta_lat = math.radians(lat2-lat1)
	delta_lot = math.radians(lon2-lon1)
	a = math.sin(delta_lat / 2) * math.sin(delta_lat / 2) + math.cos(lat3) * math.cos(lat4) * math.sin(delta_lot / 2) * math.sin(delta_lot / 2)
	c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
	d = radius * c
	return d

# define cross-track error routine
# credit: http://www.movable-type.co.uk/scripts/latlong.html
def crosstrack(distance1, azimuth, track):
	distance3 = round(abs(math.asin(math.sin(distance1 / radius) * math.sin(radians(azimuth - track))) * radius),3)
	return distance3

# define gmail mail sender routine
def send_gmail(gmail_send_user, gmail_recv_user, gmail_pwd, gmail_subject, gmail_body):
	smtpserver = smtplib.SMTP("smtp.gmail.com",587)
	smtpserver.ehlo()
	smtpserver.starttls()
	smtpserver.ehlo
	smtpserver.login(gmail_send_user, gmail_pwd)
	
	gmail_header = 'To:' + gmail_recv_user + '\n' + 'From: ' + gmail_send_user + '\n' + gmail_subject + '\n'
	gmail_msg = gmail_header + '\n' + gmail_body + '\n\n'
	smtpserver.sendmail(gmail_send_user, gmail_recv_user, gmail_msg)
	smtpserver.close()

# loop through all records from dump1090 port 8080 input stream on json
while True:
	for aircraft in myflights.aircraft:
		if (aircraft.seen_pos):
			# extract datetime/flight/lat/lon/altitude, calculate distance/azimuth, and create or update dictionary
			icao = aircraft.hex
			date_time = datetime.datetime.fromtimestamp(aircraft.now)
			flight = aircraft.flight
			if (flight == ""):
				flight = "n/a"
			lat = aircraft.lat
			lon = aircraft.lon
			altitude = aircraft.altitude
			if (altitude == "ground"):
				altitude = 0
			
			if (metric_units):
				altitude = int(round(altitude * 0.3048))
				radius = 6378.137 # km radius of the Earth
				distance1 = round(haversine((my_lat, my_lon), (lat, lon)),3)
				distance2 = round((math.sqrt((altitude - my_elevation) **2 + (distance1 * 1000) **2) / 1000),3) # convert the actual distance to the decision of the sphere
				distance4 = round(haversine((exclusion_lat, exclusion_lon), (lat, lon)),3)
				altitude_units = "m"
				distance_units = "km"
			else:
				radius = 3963.191 # miles radius of the Earth
				distance1 = round(haversine((my_lat, my_lon), (lat, lon)),3)
				distance2 = round((math.sqrt((altitude - my_elevation) **2 + (distance1 * 5280) **2) / 1000),3) # convert the actual distance to the decision of the sphere
				distance4 = round(haversine((exclusion_lat, exclusion_lon), (lat, lon)),3)
				altitude_units = "feet"
				distance_units = "mile"
			
			distance5 = 0
			distance6 = 0
			if (distance1 < warning_distance):
				distance5 = round(1000 * math.sqrt(warning_oblate ** 2 * (1 - (distance1 ** 2 / warning_distance ** 2))),3)
			if (distance1 < alert_distance):
				distance6 = round(1000 * math.sqrt(alert_oblate ** 2 * (1 - (distance1 ** 2 / alert_distance ** 2))),3)
			
			azimuth = atan2(sin(radians(lon - my_lon)) * cos(radians(lat)), cos(radians(my_lat)) * sin(radians(lat)) - sin(radians(my_lat)) * cos(radians(lat)) * cos(radians(lon - my_lon)))
			azimuth = round(((degrees(azimuth) + 360) % 360),3)
			
			track = aircraft.track
			if (aircraft.mlat and mlat_enable == False):
				track = "" # trick
			
			# plane_dict[icao] = [datetime, flight, lat, lon, altitude, distance1, distance2, azimuth, track, distance_old, (APPROACHING/RECEDING/HOLDING), (WARNING/None), (LINKED!/ENTERING/LEAVING)]
			if (icao not in plane_dict):
				plane_dict[icao] = [date_time, flight, lat, lon, altitude, distance1, distance2, azimuth, track, distance1, "", "", ""]
			else:
				plane_dict[icao][0] = date_time
				plane_dict[icao][1] = flight
				plane_dict[icao][2] = lat
				plane_dict[icao][3] = lon
				plane_dict[icao][4] = altitude
				plane_dict[icao][5] = distance1
				plane_dict[icao][6] = distance2
				plane_dict[icao][7] = azimuth
				plane_dict[icao][8] = track
				
				# figure out if plane is approaching/holding/receding
				distance_old = plane_dict[icao][9]
				if (distance1 < distance_old):
					plane_dict[icao][10] = "APPROACHING"
					plane_dict[icao][9] = distance1
				elif (distance1 > distance_old):
					plane_dict[icao][10] = "RECEDING"
				else:
					plane_dict[icao][10] = "HOLDING"
			
			# log stats to stdout and also email if entering/leaving detection zone
			if (track != ""):
				distance3 = crosstrack(distance1, (180 + azimuth) % 360, track)
				warning = plane_dict[icao][11]
				
				plane_log = str(date_time) + ", " + icao + ", " + str(flight) + ", " + str(lat) + ", " + str(lon) + ", " + str(altitude) + ", " + str(distance1)
				gmail_log = str(date_time) + " ICAO=" + icao + " FLIGHT=" + str(flight) + " LATITUDE=" + str(lat) + " LONGITUDE=" + str(lon) + " ALTITUDE=" + str(altitude) + altitude_units + " TRACK=" + str(track) + " DISTANCE=" + str(distance2) + distance_units
				
				#print plane_log
				
				if (distance3 <= 4 and distance1 < warning_distance and altitude < distance5 and distance4 > exclusion_radius and warning == "" and plane_dict[icao][10] != "RECEDING"):
					plane_dict[icao][11] = "WARNING"
					
					gmail_subject = 'Subject:WARNING: Aircraft Approaching Dectection Zone: ' + str(flight) + ' ' + str(distance2) + ' ' + distance_units
					gmail_body = gmail_log + '\n\n' + 'Predicted close encounter: ' + str(distance2) + distance_units + '\n\n'
					if (flight != "n/a"):
						gmail_body = gmail_body + 'http://flightaware.com/live/flight/' + str(flight) + '\n\n'
						gmail_body = gmail_body + 'https://planefinder.net/flight/' + str(flight) + '\n\n'
						gmail_body = gmail_body + 'http://www.flightradar24.com/' + str(flight) + '\n\n'
					send_gmail(gmail_send_user, gmail_recv_user, gmail_pwd, gmail_subject, gmail_body)
				
				if (distance3 > 4 and distance1 < warning_distance and distance4 > exclusion_radius and warning == "WARNING" and plane_dict[icao][10] != "RECEDING"):
					plane_dict[icao][11] = ""
					
					gmail_subject = 'Subject:WARNING: Aircraft Diverting Dectection Zone: ' + str(flight) + ' ' + str(distance2) + ' ' + distance_units
					gmail_body = gmail_log + '\n\n' + 'Predicted close encounter: ' + str(distance2) + distance_units + '\n\n'
					if (flight != "n/a"):
						gmail_body = gmail_body + 'http://flightaware.com/live/flight/' + str(flight) + '\n\n'
						gmail_body = gmail_body + 'https://planefinder.net/flight/' + str(flight) + '\n\n'
						gmail_body = gmail_body + 'http://www.flightradar24.com/' + str(flight) + '\n\n'
					send_gmail(gmail_send_user, gmail_recv_user, gmail_pwd, gmail_subject, gmail_body)
				
				if (plane_dict[icao][12] == ""):
					plane_dict[icao][12] = "LINKED!"
				
				# if plane enters detection zone, send email and begin history capture
				if (distance3 <= 2 and distance1 < alert_distance and altitude < distance6 and distance4 > exclusion_radius and plane_dict[icao][12] != "ENTERING"):
					plane_dict[icao][12] = "ENTERING"
					plane_hist[icao] = [plane_log.split(",")]
					
					gmail_subject = 'Subject:ALERT: Aircraft Entering Dectection Zone: ' + str(flight) + ' ' + str(distance2) + ' ' + distance_units
					gmail_body = gmail_log + '\n\n'
					if (flight != "n/a"):
						gmail_body = gmail_body + 'http://flightaware.com/live/flight/' + str(flight) + '\n\n'
						gmail_body = gmail_body + 'https://planefinder.net/flight/' + str(flight) + '\n\n'
						gmail_body = gmail_body + 'http://www.flightradar24.com/' + str(flight) + '\n\n'
					send_gmail(gmail_send_user, gmail_recv_user, gmail_pwd, gmail_subject, gmail_body)
				
				# if plane still within detection zone, add to history capture
				if (distance1 < alert_distance and distance4 > exclusion_radius and plane_dict[icao][12] == "ENTERING"):
					plane_hist[icao].append(plane_log.split(","))
				
				# if plane leaves detection zone, generate email and include history capture
				if (distance1 > alert_distance and distance4 > exclusion_radius and plane_dict[icao][12] == "ENTERING"):
					plane_dict[icao][12] = "LEAVING"
					
					gmail_subject = 'Subject:ALERT: Aircraft Leaving Dectection Zone: ' + str(flight) + ' ' + str(distance2)  + ' ' + distance_units
					gmail_body = gmail_log + '\n\n'
					if (flight != "n/a"):
						gmail_body = gmail_body + 'http://flightaware.com/live/flight/' + str(flight) + '\n\n'
						gmail_body = gmail_body + 'https://planefinder.net/flight/' + str(flight) + '\n\n'
						gmail_body = gmail_body + 'http://www.flightradar24.com/' + str(flight) + '\n\n'
					gmail_body = gmail_body + '\n\n'
					
					# mark plane history record(s) of closest approach
					for hist_entry in plane_hist[icao]:
						if (str(plane_dict[icao][9]) == str(hist_entry[6])):
							gmail_body = gmail_body + ','.join(hist_entry) + ' *** CLOSEST APPROACH ***' + '\n'
						else:
							gmail_body = gmail_body + ','.join(hist_entry) + '\n'
					del plane_hist[icao]
					gmail_body = gmail_body + '\nClosest encounter: ' + str(plane_dict[icao][9]) + distance_units
					send_gmail(gmail_send_user, gmail_recv_user, gmail_pwd, gmail_subject, gmail_body)
					
	# check age of newest icao record, compare to newly-input value, and kill dictionary if too old (i.e. start fresh history)				
	dict_list = copy.copy(plane_dict)
	for key in dict_list:
		then = plane_dict[key][0]
		now = datetime.datetime.now()
		diff_minutes = (now - then).total_seconds() / 60
		if (diff_minutes > duplicate_minutes):
			del plane_dict[key]
	
	sleep(1)
	
	# refresh the flight data
	myflights.refresh()

権限変更

$ chmod +x flight_warning.py

動作確認

$ python flight_warning.py
Ctrl+Cで停止

自動起動

$ sudo leafpad /etc/init.d/flight_warning

コピーペースト

#!/bin/sh

### BEGIN INIT INFO
# Provides:          flight_warning
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Put a short description of the service here
# Description:       Put a long description of the service here
### END INIT INFO

# Change the next 3 lines to suit where you install your script and what you want to call it
DIR=/home/pi/flight-warning
DAEMON=$DIR/flight_warning.py
DAEMON_NAME=flight_warning

# Add any command line options for your daemon here
DAEMON_OPTS=""

# This next line determines what user the script runs as.
# Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python.
DAEMON_USER=root

# The process ID of the script when it runs is stored here:
PIDFILE=/var/run/$DAEMON_NAME.pid

. /lib/lsb/init-functions

do_start () {
    log_daemon_msg "Starting system $DAEMON_NAME daemon"
    start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS
    log_end_msg $?
}
do_stop () {
    log_daemon_msg "Stopping system $DAEMON_NAME daemon"
    start-stop-daemon --stop --pidfile $PIDFILE --retry 10
    log_end_msg $?
}

case "$1" in

    start|stop)
        do_${1}
        ;;

    restart|reload|force-reload)
        do_stop
        do_start
        ;;

    status)
        status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
        ;;

    *)
        echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
        exit 1
        ;;

esac
exit 0

権限変更

$ sudo chmod 755 /etc/init.d/flight_warning

サービス登録

$ sudo update-rc.d flight_warning defaults

再起動

$ sudo reboot

dump978(資料)

参考: dump978
国内で有益な状況が見えれば設備準備したいと思います。

米国内ではADS-Bサービスに関して1090MHzと978MHzの周波数帯が用いられています。
ADS-Bの利点として周波数資源の有効利用もあるのですが、1090MHzでは既存のトランスポンダーやTCAS(衝突防止システム)に加えて1090SE(ADS-B Out)が加わり、これ以上の情報をのせる余地がありません。
そこで978MHz帯(又はUAT、Universal Access Transceiver)を用いて、ADS-B Out機能を担う事となりました。(現在米国内と中国の一部で運用されています)
978MHz帯は米国内では航空通信に用意され、現状データーリンク(気象情報等)に利用されています。 (日本の電波法は不明)
FAAでは現在の周波数資源の状況や将来に渡る拡張性を考慮し、18000フィート以上(Flight Level 180 and above = Class-A airspace)に於いては1090SE ADS-B Out、以下の高度では978SE ADS-B Outと区分けし、2020年1月1日から米国空域下での全ての航空機に適用されます。

引用: 星と無線とひこ~き ADS-Bトリヴィア

serviceの動作チェック

script作成

$ sudo leafpad check

コピーペースト

#!/bin/bash

service dump1090-mutability status
echo -e
service fr24feed status
echo -e
service piaware status
echo -e
service pfclient status
echo -e
service modesmixer2 status
echo -e
service nginx status
echo -e
service collectd status
echo -e
service flight_warning status
echo -e

権限変更

$ sudo chmod 755 check

起動

$ ./check

Active: active (running)となっていれば問題ないと思います。

crontabの設定

一ヶ月もせずフリーメモリが無くなってしまいそうです。
Rpi-memory.jpg

crontabでRaspberryPi本体を再起動設定することにします。

$ sudo leafpad /etc/crontab

毎月1日と15日の6:00に再起動
末尾に追記(分 時 日 月 曜日 コマンドの順。)

0 6 1,15 * * root shutdown -r now

再起動

$ sudo reboot

RPi-Monitorのインストール

RPi-Monitor

参考: RPi-Experiences
サーバ監視によさげです。

$ sudo apt-get install apt-transport-https ca-certificates
$ sudo wget http://goo.gl/rsel0F -O /etc/apt/sources.list.d/rpimonitor.list
$ sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 2C0D3C0F
$ sudo apt-get update && sudo apt-get install rpimonitor
$ sudo /usr/share/rpimonitor/scripts/updatePackagesStatus.pl

動作確認

PaspberryPiの場合 http://127.0.0.1:8888
他のPCの場合 http://192.168.1.184:8888

RPi-Monitorのカスタマイズ

RPi-Monitorにdump1090-mutability、fr24feed、piaware、pfclient、VirtualRadar.exe、modesmixer2、flight-warning、nginx、collectdを登録してみます。

FR24 avionics.jpg

新規作成

$ sudo leafpad /etc/rpimonitor/template/avionics.conf

コピーペースト URLなど各自の環境にあわせて修正が必要です。

########################################################################
# Extract information about Opened Port
#  Page: 1
#  Information               Status     Statistics
#  - dump1090-mutability port (8080)           - yes      - no
#  - Flightradar24 Feeder/Decoder port (8754)  - yes      - no
#  - FlightAware port (30104)          - yes      - no
#  - PlaneFinder port (30053)        - yes      - no
#  - Virtual Radar Server port (8081) - yes - no
#  - ModeSMixer2 port (8082) - yes - no
#  - nginx port (49153) - yes - no
#  - collectd port (8083) - yes - no
########################################################################
dynamic.1.name=dump1090
dynamic.1.source=netstat -nlt
dynamic.1.regexp=tcp .*:(8080).*LISTEN

dynamic.2.name=fr24
dynamic.2.source=netstat -nlt
dynamic.2.regexp=tcp .*:(8754).*LISTEN

dynamic.3.name=piware
dynamic.3.source=netstat -nlt
dynamic.3.regexp=tcp .*:(30104).*LISTEN

dynamic.4.name=finder
dynamic.4.source=netstat -nlt
dynamic.4.regexp=tcp .*:(30053).*LISTEN

dynamic.5.name=vrs
dynamic.5.source=netstat -nlt
dynamic.5.regexp=tcp .*:(8081).*LISTEN

dynamic.6.name=mixer2
dynamic.6.source=netstat -nlt
dynamic.6.regexp=tcp .*:(8082).*LISTEN

dynamic.7.name=nginx
dynamic.7.source=netstat -nlt
dynamic.7.regexp=tcp .*:(49153).*LISTEN

dynamic.8.name=collectd
dynamic.8.source=netstat -nlt
dynamic.8.regexp=tcp .*:(8083).*LISTEN

dynamic.9.name=warning
dynamic.9.source=ps -axg
dynamic.9.regexp=flight_warning.py$

static.1.name=dump1090ver
static.1.source=dump1090-mutability --help
static.1.regexp=dump1090-mutability v(.*\b)

static.2.name=fr24ver
static.2.source=fr24feed --help
static.2.regexp=Version: (.*)/

static.3.name=piwarever
static.3.source=piaware -v
static.3.regexp=(^.*)

static.4.name=finderver
static.4.source=pfclient -v
static.4.regexp=version:(.*)

static.5.name=mixer2ver
static.5.source=/home/pi/modesmixer2/modesmixer2 --help
static.5.regexp=ModeSMixer2 v.(.*)

static.6.name=nginxver
static.6.source=nginx -v
static.6.regexp=(^.*)

web.status.1.content.1.name=Avionics
web.status.1.content.1.icon=daemons.png
web.status.1.content.1.line.1="<table class='table'><thead><tr><th>Application</th><th>Status</th><th>Version</th><th>Web interface</th></tr></thead><tbody>"
web.status.1.content.1.line.2="<tr>"
web.status.1.content.1.line.3="<td><a href='https://github.com/mutability/dump1090' target=_blank>dump1090-mutability</a></td><td>" + Label(data.dump1090,"==8080","active","success") + Label(data.dump1090,"!=8080","inactive","danger") + "</td><td>" + data.dump1090ver + "</td><td><a href='http://192.168.1.184:8080/' target=_blank>DUMP1090</a></td>"
web.status.1.content.1.line.4="</tr>"
web.status.1.content.1.line.5="<tr>"
web.status.1.content.1.line.6="<td><a href='http://feed.flightradar24.com/#raspberry-pi' target=_blank>fr24feed</a></td><td>" + Label(data.fr24,"==8754","active","success") + Label(data.fr24,"!=8754","inactive","danger") + "</td><td>" + data.fr24ver + "</td><td><a href='http://192.168.1.184:8754' target=_blank>FR24 Feeder Status</a><br /><a href='https://www.flightradar24.com/premium/' target=_blank>Flightradar24.com Premium</a></td>"
web.status.1.content.1.line.7="</tr>"
web.status.1.content.1.line.8="<tr>"
web.status.1.content.1.line.9="<td><a href='http://ja.flightaware.com/adsb/piaware/' target=_blank>piaware</a></td><td>" + Label(data.piware,"==30104","active","success") + Label(data.piware,"!=30104","inactive","danger") + "</td><td>" + data.piwarever + "</td><td><a href='https://flightaware.com/adsb/stats/user/shogooda' target=_blank>FlightAware</a></td>"
web.status.1.content.1.line.10="</tr>"
web.status.1.content.1.line.11="<tr>"
web.status.1.content.1.line.12="<td><a href='https://planefinder.net/sharing/client' target=_blank>pfclient</a></td><td>" + Label(data.finder,"==30053","active","success") + Label(data.finder,"!=30053","inactive","danger") + "</td><td>" + data.finderver + "</td><td><a href='http://192.168.1.184:30053' target=_blank>Plane Finder Client</a></td>"
web.status.1.content.1.line.13="</tr>"
web.status.1.content.1.line.14="<tr>"
web.status.1.content.1.line.15="<td><a href='http://www.virtualradarserver.co.uk/Download.aspx' target=_blank>VirtualRadar.exe</a></td><td>" + Label(data.vrs,"==8081","active","success") + Label(data.vrs,"!=8081","inactive","danger") + "</td><td>2.3.1.41765</td><td><a href='http://192.168.1.184:8081/VirtualRadar/WebAdmin/Index.html' target=_blank>VRS Web Admin</a><br /><a href='http://192.168.1.184:8081/VirtualRadar' target=_blank>Virtual Radar Server</a><br /><a href='http://www.adsbexchange.com/' target=_blank>ADS-B Exchange</a></td>"
web.status.1.content.1.line.16="</tr>"
web.status.1.content.1.line.17="<tr>"
web.status.1.content.1.line.18="<td><a href='http://xdeco.org/?page_id=30#mm2' target=_blank>modesmixer2</a></td><td>" + Label(data.mixer2,"==8082","active","success") + Label(data.mixer2,"!=8082","inactive","danger") + "</td><td>" + data.mixer2ver + "</td><td><a href='http://192.168.1.184:8082' target=_blank>ModeSMixer2</a><br /><a href='http://sdrsharp.com:8080/virtualradar/' target=_blank>Airspy ADSB Hub</a></td>"
web.status.1.content.1.line.19="</tr>"
web.status.1.content.1.line.20="<tr>"
web.status.1.content.1.line.21="<td><a href='https://github.com/darethehair/flight-warning' target=_blank>flight-warning</a></td><td>" + Label(data.warning,"==1","active","success") + Label(data.warning,"!=1","inactive","danger") + "</td><td>1.06</td><td></td>"
web.status.1.content.1.line.22="</tr>"
web.status.1.content.1.line.23="<tr>"
web.status.1.content.1.line.24="<td>nginx</td><td>" + Label(data.nginx,"==49153","active","success") + Label(data.nginx,"!=49153","inactive","danger") + "</td><td></td><td><a href='http://192.168.1.180/myradar24/' target=_blank>MyRadar24</a></td>"
web.status.1.content.1.line.25="</tr>"
web.status.1.content.1.line.26="<tr>"
web.status.1.content.1.line.27="<td>collectd</td><td>" + Label(data.collectd,"==8083","active","success") + Label(data.collectd,"!=8083","inactive","danger") + "</td><td></td><td><a href='http://192.168.1.184:8083' target=_blank>Dump1090 Graphs</a></td>"
web.status.1.content.1.line.28="</tr>"
web.status.1.content.1.line.29="</tbody></table>"

avionics.confの登録

$ sudo leafpad /etc/rpimonitor/data.conf

追記 (include=/etc/rpimonitor/template/version.confの下辺り)

include=/etc/rpimonitor/template/avionics.conf

もうひとつ、温度(Temperature)の下限が40度でRaspberryPiではメーターが動かないことがあります。
30度であればよさげです。

$ sudo leafpad /etc/rpimonitor/template/temperature.conf
web.status.1.content.4.line.1=JustGageBar("Temperature", "ーC", 40, data.soc_temp, 80, 100, 80)
を
web.status.1.content.4.line.1=JustGageBar("Temperature", "ーC", 30, data.soc_temp, 80, 100, 80)

再起動

$ sudo /etc/init.d/rpimonitor restart

動作確認

PaspberryPiの場合 http://127.0.0.1:8888
他のPCの場合 http://192.168.1.184:8888

MLATの表示設定

MLAT表示してないアプリをMLAT対応にしてみます。
※PiAwareに依存する設定になってしまいます。PiAwareに不具合が出ると連鎖してしまうと思います。

PiAwareの設定変更

piaware-configの書き換え

$ sudo piaware-config -mlatResultsFormat "beast,connect,localhost:30104 ext_basestation,listen,30106 basestation,listen,31003"
$ sudo piaware-config -restart
$ sudo piaware-config -show

Virtual Radar Serverの場合

大雑把な設定の流れは、
Receiver1を停止
Receiver2に127.0.0.1:30005ポート(dump1090-mutabilityのBeastフォーマットアウトプット)を設定
Receiver3に127.0.0.1:30106ポート(PiAwareのExtendedBasestationフォーマットアウトプット)を設定
Merged FeedsでReceiver2とReceiver3をマージ
Virtual Radar Serverの表示設定にMLATを追加

VRS Web Adminの表示

PaspberryPiの場合 http://192.168.1.184:8081/VirtualRadar/WebAdmin/Index.html
他のPCの場合 http://127.0.0.1:8081/VirtualRadar/WebAdmin/Index.html

Receivers設定済みの状態
MLAT002.jpg

Receiver2の設定
MLAT003.jpg

Receiver3の設定
MLAT004.jpg

Merged Feeds設定済みの状態
MLAT005.jpg

Merged Feedの設定
MLAT006.jpg

Virtual Radar Serverの表示設定

PaspberryPiの場合 Desktop Version http://127.0.0.1:8081/VirtualRadar/desktop.html
他のPCの場合 Desktop Version http://192.168.1.184:8081/VirtualRadar/desktop.html

MLAT007.jpg

MLAT008.jpg

PlaneFinderの場合

Virtual Radar Server経由成功しませんでした。ModeSMixer2経由試してません。

ModesMixer2の場合

書き換え

$ sudo leafpad /etc/init.d/modesmixer2

--inConnect 127.0.0.1:31003(PiAwareのBasestationフォーマットアウトプット)の追記
--location 35.xxxx:139.xxxxは観測地の緯度経度

DAEMON_ARGS="--inConnect 127.0.0.1:31003 --inConnect 127.0.0.1:30005 --outConnect avr:sdrsharp.com:47806 --location 35.xxxx:139.xxxx --web 8082 --db /home/pi/modesmixer2/BaseStation.sqb --frdb /home/pi/modesmixer2/flightroute_w.sqb --silhouettes /home/pi/modesmixer2/Silhouettes"

サービス再起動

$ sudo service modesmixer2 restart

MLAT表示が無いので分かりにくいです。dump1090-mutabilityと比較すると表示はしてるようです。

Airspy ADSB Hubの場合

ModesMixer2を設定しておけば自動的にMLAT対応になると思います。
Virtual Radar Server同様のMLAT表示設定にしても全て「No」で区別してないように見えます。
dump1090-mutabilityと比較すると表示はしてるようです。

ADS-B Exchangeの場合

上記Virtual Radar Serverの設定に加えRebroadcastの設定変更をします。

MLAT009.jpg

MLAT010.jpg

Virtual Radar Server同様のMLAT表示設定にしても全て「No」で区別してないように見えます。
dump1090-mutabilityと比較すると表示はしてるようです。

SimpleRadar24

SimpleRadar24
目黒川リバーサイドレストランMAP
目黒川リバーサイドレストランMAP

ただ単に飛行機を表示するだけの最小構成の雛形作りました。
限定的な使い方でしょうが地域振興ホームページに採用してみようと試みてます。

目黒川リバーサイドレストランMAP

飛行機素材
Aircraft.png

雛形HTML

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>SimpleRadar24</title>
<link href="http://openlayers.org/en/v3.18.2/css/ol.css" rel="stylesheet" type="text/css">
<style>
html, body {
	margin: 0;
	padding: 0;
}
.map {
	height: 100%;
	width: 100%;
	position: fixed;
}
</style>
<script src="http://openlayers.org/en/v3.18.2/build/ol.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
$(window).on('load', function() {
	/**********************/
	// setting
	/**********************/
	var myLat = 35.******;
	var myLon = 139.******;
	var jsonURL = 'http://******/data/aircraft.json';
	var removeTime = 10; //Time to delete from lost (s)
	
	var features = [];
	var nowList = [["dummy", 9999999999]];
	setInterval(function() {
		$.ajax({
			type: 'GET',
			url: jsonURL,
			cache: false,
			dataType: 'json',
			success: function(json) {
				//console.log('success');
				$.each(json.aircraft, function(i) {
					if (this.hex && this.lat && this.lon && this.track) {
						var newCoord = new ol.geom.Point(ol.proj.transform([this.lon, this.lat], 'EPSG:4326', 'EPSG:3857'));
						var style = [
							new ol.style.Style({
								image: new ol.style.Icon({
									anchorXUnits: 'fraction',
									anchorYUnits: 'fraction',
									src: 'aircraft.png',
									rotation: this.track * Math.PI / 180,
									opacity: 0.6,
									scale: 0.5
								})
							})
						];
						finded = false;
						for (var i = 0; i < nowList.length; i++) {
							if (nowList[i][0] == this.hex) {
								nowList.splice(i, 1, [this.hex, Math.round(+new Date() / 1000)]);
								finded = true;
							}
						}
						if (finded) {
							for (var i = 0; i < features.length; i++) {
								if (features[i].getId() == this.hex) {
									features[i].set('geometry', newCoord);
									features[i].setStyle(style);
									finded = false;
									break;
								}
							}
						} else {
							nowList.push([this.hex, Math.round(+new Date() / 1000)]);
							feature = new ol.Feature({
								geometry: newCoord
							});
							feature.setId(this.hex);
							feature.setStyle(style);
							vectorSource.addFeature(feature);
						}
					}
				});
			},
			error: function(json) {
				//console.log('error');
			}
		}).done(function() {
			deleted = false;
			delFeature = '';
			for (var i = 0; i < nowList.length; i++) {
				timestamp = nowList[i][1];
				if ((timestamp + removeTime) < Math.round(+new Date() / 1000)) {
					delFeature = nowList[i][0];
					nowList.splice(i, 1);
					deleted = true;
				}
			}
			if (deleted) {
				for (var i = 0; i < features.length; i++) {
					if (features[i].getId() == delFeature) {
						vectorSource.removeFeature(features[i]);
						deleted = false;
						break;
					}
				}
			}
		}).fail(function() {
			//console.log('error');
		});
	}, 1000);
	
	var vectorSource = new ol.source.Vector({
		features: features,
		useSpatialIndex: false
	});
	var map = new ol.Map({
		target: 'map',
		renderer: 'canvas',
		view: new ol.View({
			center: ol.proj.transform([myLon, myLat], 'EPSG:4326', 'EPSG:3857'),
			zoom: 9
		}),
		layers: [
			new ol.layer.Tile({
				source: new ol.source.Stamen({
					layer: 'toner'
				}),
				opacity: 0.5
			}),
			new ol.layer.Vector({
				source: vectorSource,
				updateWhileAnimating: true,
				updateWhileInteracting: true
			})
		],
		controls: [],
		loadTilesWhileAnimating: true,
		loadTilesWhileInteracting: true
	});
});
</script>
</head>
<body>
<div id="map" class="map"></div>
</body>
</html>

Flightradar24からJSON取得

参考: JSON - Flightradar24 から飛行中の航空機情報を取得!
参考: Cykey’s blog — Analyzing Flightradar24’s internal API structure

いろんな情報取れるのでFlightradar24規模のアプリケーション簡単に作れると思いきやJSONに警告入ってました。個人利用程度にしておきます。

The contents of this file and all derived data are the property of Flightradar24 AB for use exclusively by its products and applications. Using, modifying or redistributing the data without the prior written permission of Flightradar24 AB is not allowed and may result in prosecutions.
このファイルおよびすべての引き出されたデータの内容は、その製品とアプリケーションによって独占的に使用のためのFlightradar24 ABのプロパティである。Flightradar24 ABの事前許可なしにデータを使うこと、修正すること、または再配布することは許されず、起訴を結果として生じるかもしれない。

Windowsアプリ

ModeSMixerと連携して同じネットワーク内のWindowsで操作してみます。

BaseStation

BaseStation

参考: BaseStation

■RaspberryPi側の設定

$ sudo leafpad /etc/init.d/modesmixer2

ModeSMixer2に
--outServer sbs10001:10001を追加
--location 35.xxxx:139.xxxxは観測地の緯度経度

DAEMON_ARGS="--inConnect 127.0.0.1:30005 --outConnect avr:sdrsharp.com:47806 --outServer sbs10001:10001 --location 35.xxxx:139.xxxx --web 8082 --db /home/pi/modesmixer2/BaseStation.sqb --frdb /home/pi/modesmixer2/flightroute_w.sqb --silhouettes /home/pi/modesmixer2/Silhouettes"

ModeSMixer2再起動

$ sudo /etc/init.d/modesmixer2 restart

■BaseStation側の設定
BaseStation_184 CDダウンロード

http://www.kinetic.co.uk/basestationdownloads1.php

解凍しインストール

setup.exe

起動 BaseStation Startup Configurationはとりあえずキャンセル
Settings→Hardware Settings→Network

  • Adress: 192.168.1.184
  • Port: 10001

FR24 BaseStation.jpg

Settings→Data Source→Use Networkにチェックマーク
以後は起動の度に自動接続です

BaseStationのカスタマイズ 参考: KG-ACARS HFDL VDL MCAに感謝 受信方法 受信記録のブログPlus RTL-SDR

adsbSCOPE

adsbSCOPE

参考: adsbSCOPE

ダウンロード

http://www.sprut.de/electronic/pic/projekte/adsb/adsb_en.html#downloads

ZIPファイルの解凍
解凍されたadsb_allフォルダがプログラム群です。
通常のインストールフォルダC:\Program Files (x86)にadsb_allフォルダを移動

adsbscope27_256.exeの起動

adsbscope27_256.exeとadsbscope27_16384.exeの違いは扱えるデーター量の違いのようです。

other→Network→Network setup

各項目設定
FR24 adsbSCOPE03.jpg

このボタンで受信開始
FR24 adsbSCOPE06.jpg

以後はこのボタンで動きます。

PlanePlotter(有料アプリ)

参考: PlanePlotter

■RaspberryPi側の設定

$ sudo leafpad /etc/init.d/modesmixer2

ModeSMixer2に--outServer beast:31001を追加
--location 35.xxxx:139.xxxxは観測地の緯度経度

DAEMON_ARGS="--inConnect 127.0.0.1:30005 --outConnect avr:sdrsharp.com:47806 --outServer sbs10001:10001 --outServer beast:31001 --location 35.xxxx:139.xxxx --web 8082 --db /home/pi/modesmixer2/BaseStation.sqb --frdb /home/pi/modesmixer2/flightroute_w.sqb --silhouettes /home/pi/modesmixer2/Silhouettes"

ModeSMixer2再起動

$ sudo /etc/init.d/modesmixer2 restart

■PlanePlotter側の設定

ダウンロードとインストール

http://www.coaa.co.uk/planeplotter.htm#download

起動 Options→I/O Settings

FR24 PlanePlotter.jpg

次に

FR24 PlanePlotter02.jpg

FR24 PlanePlotter03.jpg

受信開始

FR24 PlanePlotter04.jpg

これで受信してる様子は見えるのですが使い方がわかりません。すいません。

航空機自動追跡録画装置

Raspberry Pi カメラモジュール V2
Adafruit 16-Channel 12-bit PWM/Servo Driver
SG-90サーボ用 2軸 カメラマウント

観測地の上空を飛ぶ航空機を追跡し録画、ビデオクリップをウェブサイトにアップロード公開します。
参考: The Pi Plane Project

書き込み中

IMG 6071-900x600.jpg

The Pi Plane Projectから若干変更してみます。

  • セルフパワーUSB未使用
  • Adafruit Pi T-Cobbler Plus Kit未使用
  • LCD未使用
  • サーボドライバー使用
  • GPS座標から航空機捕獲後はopenCVで追従
  • その他ソース改良

カメラマウントの工作

カメラマウント002.jpg

ライブラリ設定: Adafruit 16 Channel Servo Driver with Raspberry Pi
組立参考: SG90サーボ用の2軸カメラマウントが到着
配線参考: 首ふりwebカメラを作ってみる(4)(サーボ制御編) - うつなエンジニアの日記

後にSG-90サーボのセンター位置を出すのでホーンにサーボを固定しない程度に仮組み
※先に組み上げカメラマウント可動範囲以上に回転させてしまいサーボ数個壊してしまいました。ご注意。
※それと、ホーンにサーボをねじ込む際に必要以上に力を加えたようでサーボ数個壊してしまいました。ご注意。

Raspberry Piの設定

Menu→設定→Raspberry Pi の設定
インターフェイスタブ→I2C有効→OK
$ sudo reboot

ライブラリインストール

$ sudo apt-get install python-smbus
$ sudo apt-get install i2c-tools

確認

$ sudo i2cdetect -y 1

0x40に40とあればOKと思います。

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: 70 -- -- -- -- -- -- --

Adafruitライブラリのインストール

$ git clone https://github.com/adafruit/Adafruit_Python_PCA9685.git
$ cd Adafruit_Python_PCA9685
$ sudo python setup.py install

動作確認スクリプトをPiPlaneSoftware動作範囲に書き換え

$ cd examples
$ sudo leafpad simpletest.py

コピーペースト
pwm.set_pwm(X, 0, …のXはサーボ番号に変更

# Simple demo of of the PCA9685 PWM servo/LED controller library.
# This will move channel 0 from min to max position repeatedly.
# Author: Tony DiCola
# License: Public Domain
from __future__ import division
import time
import math

# Import the PCA9685 module.
import Adafruit_PCA9685


# Uncomment to enable debug output.
#import logging
#logging.basicConfig(level=logging.DEBUG)

# Initialise the PCA9685 using the default address (0x40).
pwm = Adafruit_PCA9685.PCA9685()

# Alternatively specify a different address and/or bus:
#pwm = Adafruit_PCA9685.PCA9685(address=0x41, busnum=2)

# Configure min and max servo pulse lengths
#servo_min = 150  # Min pulse length out of 4096
#servo_max = 600  # Max pulse length out of 4096

# Configure min and max servo degree
# horizon degree 15-165, sky degree 0-70
# pulse lengths = 150 + degree * (600-150) / 180
ho_offset = 0 # less than 15
ho_min_degree = 15
ho_max_degree = 165
ho_mid_degree = 90
sky_offset = 0 # less than 20
sky_min_degree = 70
sky_max_degree = 0

pulse_min = 150
value = 2.5
ho_servo_min = int(round(ho_offset + pulse_min + ho_min_degree * value))
ho_servo_max = int(round(ho_offset + pulse_min + ho_max_degree * value))
ho_servo_mid = int(round(ho_offset + pulse_min + ho_mid_degree * value))
sky_servo_min = int(round(sky_offset + pulse_min + sky_min_degree * value))
sky_servo_max = int(round(sky_offset + pulse_min + sky_max_degree * value))

# Helper function to make setting a servo pulse width simpler.
def set_servo_pulse(channel, pulse):
    pulse_length = 1000000    # 1,000,000 us per second
    pulse_length //= 60       # 60 Hz
    print('{0}us per period'.format(pulse_length))
    pulse_length //= 4096     # 12 bits of resolution
    print('{0}us per bit'.format(pulse_length))
    pulse *= 1000
    pulse //= pulse_length
    pwm.set_pwm(channel, 0, pulse)

# Set frequency to 60hz, good for servos.
pwm.set_pwm_freq(60)

print('Moving servo on channel 4,7, press Ctrl-C to quit...')
while True:
    # Move servo on channel O between extremes.
    pwm.set_pwm(4, 0, ho_servo_min)
    pwm.set_pwm(7, 0, sky_servo_max)
    time.sleep(1)
    pwm.set_pwm(4, 0, ho_servo_mid)
    pwm.set_pwm(7, 0, sky_servo_min)
    time.sleep(1)
    pwm.set_pwm(4, 0, ho_servo_max)
    pwm.set_pwm(7, 0, sky_servo_max)
    time.sleep(1)
    pwm.set_pwm(4, 0, ho_servo_mid)
    pwm.set_pwm(7, 0, sky_servo_min)
    time.sleep(1)

動作確認

$ sudo python simpletest.py
Ctrl+Cで停止

SG-90サーボのセンター位置の割り出し

$ sudo leafpad simpletest.py

書き換え
pwm.set_pwm(X, 0, …のXはサーボ番号に変更

    # Move servo on channel O between extremes.
    #pwm.set_pwm(4, 0, ho_servo_min)
    #pwm.set_pwm(7, 0, sky_servo_max)
    #time.sleep(1)
    #pwm.set_pwm(4, 0, ho_servo_mid)
    #pwm.set_pwm(7, 0, sky_servo_min)
    #time.sleep(1)
    #pwm.set_pwm(4, 0, ho_servo_max)
    #pwm.set_pwm(7, 0, sky_servo_max)
    #time.sleep(1)
    pwm.set_pwm(4, 0, ho_servo_mid)
    pwm.set_pwm(7, 0, sky_servo_min)
    time.sleep(1)

実行

$ sudo python simpletest.py
Ctrl+Cで停止

ホーンの溝ピッチ間隔の制約でカメラマウントを組み上げるとセンター位置にズレが生じます。
ho_offsetとsky_offsetで微調整できます。
※想定外の挙動でサーボを壊してしまう可能性があるので最後まで仮組みをオススメします。

カメラモジュール用ケースの工作

カメラマウント003.jpg

参考: ラズパイのカメラモジュール用ケースの自作

FRISKケースを加工しました。
幅はちょうどいい感じで高さは比較的自由なので作りやすいんですが、厚みが若干足りません。
アロンアルファを厚盛りしてサンドペーパーで調整しました。プラ板挟むほうが簡単に感じます。
ホットボンドで固定しました。

この方法でカメラマウントに取り付けると映像が上下逆になります。ソフトウエア側で反転させる必要があります。

カメラの動作チェック

簡易ストリーミングで確認してみます。

Raspberry Piの設定

Menu→設定→Raspberry Pi の設定
インターフェイスタブ→カメラ有効→OK
$ sudo reboot

raspberryPi側

$ sudo apt-get install vlc
$ raspivid -o - -t 9999999 -w 1280 -h 720 -b 800000 --vflip | cvlc -vvv stream:///dev/stdin --sout '#standard{access=http,mux=ts,dst=:8085}' :demux=h264

受信側
同じネットワーク内のWindowsパソコンのVLCメディアプレーヤー

メディア → ネットワークストリームを開く → http://192.168.1.184:8085/ を入力 → 再生

PiPlaneSoftwareの用意

$ cd ~
$ wget http://simonaubury.com/wp-content/uploads/2014/09/PiPlaneSoftware.zip
バックアップ $ wget http://dz.plala.jp/wiki_data/PiPlaneSoftware.zip
$ unzip PiPlaneSoftware.zip
$ cd PiPlaneSoftware
$ mkdir sqlite
$ cd sqlite
$ wget http://www.virtualradarserver.co.uk/Files/StandingData.sqb.gz
バックアップ $ wget http://dz.plala.jp/wiki_data/StandingData.sqb.gz
$ tar -zxvf StandingData.sqb.gz
解凍エラー出るので解凍ファイルを直接wget
$ wget http://dz.plala.jp/wiki_data/StandingData.sqb
$ wget http://www.virtualradarserver.co.uk/Files/BaseStation.zip
バックアップ $ wget http://dz.plala.jp/wiki_data/BaseStation.zip
$ unzip BaseStation.zip
$ cd /home/pi/PiPlaneSoftware
$ mkdir log
$ mkdir pic

piplanepicture.pyの書き換え

$ cd /home/pi/PiPlaneSoftware
$ sudo leafpad piplanepicture.py

機器の具合

CPU使用量 数%程度

$ top コマンドでCPUやメモリ使用量の確認ができます。
Raspberry Piの発熱は気にならない程度です。

電気料金 65円/月 1050円/年

ワットチェッカーplus使用。
東京電力 従量電灯B 第3段階料金 29円93銭 で計算。
常用しても気にならない程度と思います。

DVB-Tの発熱が激しい

放熱プレート+小型ヒートシンク取り付けても恐いくらいです。
アルミケースなどに貼り付けて放熱面積大きくした方が安心できると思います。
参考対処方法
TV28Tv2DVB-T(R820T) 専用放熱キットを取り付けます
TV28Tv2DVB-T(R820T) 発熱温度、放熱プレート+ヒートシンクの放熱について検証

備忘録

検証してない備忘録載せておきます。

FlightAwareのMLATエラー対処

NTPデーモンのiburstとminpollとmaxpollを疑っています。
参考: NICTのポーリング間隔(アクセス回数)
FR24 alert.jpg

flight-warning

flight-warningの設定で満足できないので改良を模索してます。

* メール送信ログをGPXとKML対応
* 現在のdump1090-mutabilityはOpenlayersに変更されてるようなのでDrag&DropでGPXとKML読み込み
* スクォーク7x00の強制ログ記録のメール送信
* 小型モニターつけてスクォーク7x00警報や航空機異常接近の衝突確率、活動限界警報

Eva alert.png

高解像度降水ナウキャスト

参考: weatherbox/rainmap
高解像度降水ナウキャストの降水量地図レイヤを取り出しdump1090に合成できるようです。
ライセンス的に問題なのかも?個人使用で検証してみます。
最新のdump1090-mutabilityはアメリカ国立気象局ドップラーレーダー網NEXRADの地図レイヤを使えるようです。
置き換えできるようであれば試してみます。
高解像度降水ナウキャスト.jpg

earth :: 地球の風、天気、海の状況地図

参考: weatherbox/windmap
earthが自前で作れるようです。
ライセンス的に問題なのかも?個人使用で検証してみます。
Earth.jpg

dump1090 heatmap & rangeview

参考: dump1090-mutability heatmap & rangeview
一度挑戦してみたんですが動作が不安定で記事にしませんでした。再挑戦で成功しましたら記事にしてみます。
FR24 with heatmap.jpg

ADS-Bデータから飛行経路の視覚化

参考: Flight path visualizations from ADSB data
参考: GitHub - Wilm0r/plot1090: Some map visualisations of ADS-B message logs
参考: GitHub - toofishes/plot1090: Simple scripts to process and plot dump1090 data
Plot1090.png

変更履歴