VPSで環境構築する

#はじめに
このページは，

[ドローン操作システムを作ろう](https://qiita.com/hsgucci/items/86eedb5555b4234ee0e7)

の１ページです．
全体を見たい場合は上記ページへお戻りください．


#概要

これまでは，ローカルなネットワーク(イントラネット)でドローンの操作を行ってきました．
しかしやはり，グローバルネットワーク(インターネット)を介して操作したいものです．

今回はVPS(バーチャル・プライベート・サーバー）でサーバー構築してみます．

しかし，

- OSにはUbuntuを使う
- MQTTブローカーとして mosquitto を使う
- Webサーバーとして Apache を使う

という点は，これまでLinux PCでやってきた作業と同じです．

#準備するもの

何かしらのVPSサービスに加入(購入)してください．

有名どころだと，以下の２つかと思います．

- [さくらのVPS](https://vps.sakura.ad.jp/)
- [このはVPS](https://www.conoha.jp/vps/?btn_id=header_vps)

　参考：[VPS比較ランキング２０１９完全版！これ見て選べば初心者でも安心](https://sabarentalserver.com/vps/hikakuranking/)

今回の記事は，
「[さくらのVPS](https://vps.sakura.ad.jp/specification/)　CPU：仮想1Core　メモリ：512MB　SSD: 20GB」
のモデルで書きました．

##VPSについて

簡単にVPSの説明を．

**<font color="blue">筆者個人の独断と偏見によるサーバーサービスのレベル分け</font>**を書いておきます．
Linuxの知識がどの程度必要か，Linuxガチ度で分類しています

１位　専用サーバー
　　　　どこかにあるデータセンターのサーバーＰＣを１台まるごとレンタル.
　　　　「自分の手元ではなく，遠くにあるパソコン」なので，
　　　　操作をミスったら，遠くにいる管理者様に操作してもらうことになる．
　　　　設定失敗して起動しなくなったら，修正を有料でお願いするしかない．
　　　　ＯＳ再インストールも有料．
　　　　失敗の許されないサムライの仕様．ガチレベルMAX

２位　VPS(バーチャル・プライベート・サーバー）
　　　　物理ＰＣ上で動く仮想ＰＣ(エミュレーション)を1台レンタル.
　　　　「仮想専用サーバー」とも言う．
　　　　VMwareやVirtualBox等の仮想ＰＣでLinuxを動かすのと同じ．
　　　　設定失敗したらWebのGUIでサクッとOS再インストール．
　　　　管理者様がいなくても必要なことは自分でできる．
　　　　Raspberry Piで喩えれば「起動しなくなったけど，SDカード書き換えてすぐ動いた」という感じ．
　　　　失敗してもすぐリスポーン繰り返せるヌルゲー仕様．

３位　レンタルサーバー
　　　　あるサーバーＯＳ上のユーザアカウントを１つレンタル．
　　　　ＰＣ自体は他のユーザーと共用．
　　　　管理者権限は無いから，新しい機能のインストールとかはできない．
　　　　予め準備された世界の中で遊ぶだけのエンドユーザ仕様．
　　　　（準備された世界＝お釈迦様の掌の上　と呼ぶ）
　　　　使いみちはWebページとメールアカウントぐらい？

４位　クラウド
　　　　Linuxとかソケット通信とかが分からない情弱でもWebGUIで簡単に使えますように(祈り
　　　　機械学習とかなんでも使えて至れり尽くせり．
　　　　ただし，従量課金なのでミスったら莫大な請求が．．．

というわけで，
Raspberry Piの様なLinuxボード経験者であれば，VPSは十分に運用できます．
昔の組込Linuxボードだと，起動しなくなった際にイメージを焼き直すのが大変でした．
OSのインストールを何度でもやり直せる，すなわち何度でも失敗できる，というのはVPSの強みです．


##大手クラウドサービス
もちろん，大手クラウドサービスのVPS機能を使っても良いと思います．

- **AWS**(Amazon Web Services) - Amazon EC2 / Lightsail
- **GCP**(Google Cloud Platform) - Google Compute Engine
- **Azure**(Microsoft Azure) - Microsoft Virtual Machine

が，トラフィックで従量課金というのは，
昔で言う「パケ死」が怖くて筆者はまだ試していません((((；ﾟДﾟ))))
　参考：[クラウド破産について](https://cloud-textbook.com/84/#4.2)

#システム構成

システム構成は以下の図の様になります．
ドローンの代わりにシミュレータ(dronekit-sitl)を使っています．
![vps_system.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/ad848d02-1aaa-f5db-6f9f-09834fe4534b.png)


#OSの再インストール(さくらのVPS)

ここでは，さくらのVPSを使って例を示します．
他のVPSサービスを使う時は，そのサービス会社の管理システムを使ってください．

さくらのVPS登録完了直後は，何かのOSがインストールされています．(たぶんCentOS）
本企画ではUbuntuを使っていますので，ディストリビューションが違うとややこしいですね．
なので，まずはいきなりOSを再インストールしましょう．
（サーバーがうまく動かなくなった時，ハックされて乗っ取られた時なども同様の方法で再インストールします）

OSインストールを選択します．
![さくらVPS_02_install.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/c012479b-20d4-30df-5b54-afe69c733886.png)

特別なOSは必要ないので，標準OSを選択します．
![さくらVPS_03_standard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/3308adc7-909f-9bbf-f44c-e9559b5a951c.png)

Ubuntu 18.04LTSはまだ無いので，16.04LTSにします．
![さくらVPS_04_selectOS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/c7c0408f-f780-0df4-c5b3-98a08c4b8ffd.png)

**<font color="red">初期ユーザー名はubuntuに固定</font>**です．
パスワードには，さくらインターネットから渡されたパスワードを使っておくと管理が楽です．
![さくらVPS_05_password.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/013c5cb3-8b7b-e08e-33e4-0499350ebc1f.png)

スタートアップスクリプトの項目では，筆者は[Ubuntu_ufw]を選択しました．
ファイアーウォール(iptables)を有効にして，許可したポートしかアクセスできないようにするためです．

[利用しない]を選択してもかまいませんが，グローバルIPを持ったサーバが丸裸状態になるのでハッキングに気をつける必要があります．
手動でファイアウォールをインストールすべきです．
![さくらVPS_06_script.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/bd53d363-ee6b-81b6-c562-e630d91d3c48.png)

設定が完了したら[<font color="green">設定内容を確認する</font>]ボタンを押し，
確認画面で[<font color="red">インストールを実行</font>]を押してインストールを開始します．

しばらく待っていると，インストールが完了してUbuntuが起動します．
後は，TeraTermやPuttyといったSSHクライアントで接続してログインします．

また，さくらの管理画面にはシリアルコンソールがあるので，
ファイアーウォールのポート設定をミスった時などの緊急時にはこれを利用して管理することもできます．
![さくらVPS_07_console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/8ed65542-bce8-c5af-accc-e13f4a059cde.png)


#システムの更新

まずはaptのリポジトリのリストを最新のものに更新(update)し，
更新があるソフトをアップグレード(upgrade)します．

```bash:アップデート情報の更新
$ sudo apt update
$ sudo apt upgrade -y
```

#MQTTブローカーのインストール

Mosquittoのインストールはaptで一発です．
関連ツールも一応インストールしておきます．

```bash:mosquittoのインストール
$ sudo apt install mosquitto
$ sudo apt install mosquitto-clients
```

WebブラウザでMQTTを直接送受信することはできないので，

　[MQTTブローカーの設定](https://qiita.com/hsgucci/items/3d07e36320115c29cc9c#mqtt%E3%83%96%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%BC%E3%81%AE%E8%A8%AD%E5%AE%9A)

と同様にMQTT over WebSocketの設定をします．

mosquittoの設定ファイル
`/etc/mosquitto/mosquitto.conf`
を編集します．
その際，管理者権限が必要なのでsudoを付けます．

```bash:nanoで編集する場合
$sudo nano /etc/mosquitto/mosquitto.conf
```
`mosquitto.conf`ファイルの先頭に，以下の行を追加します

```
listener 1883
listener 15675
protocol websockets
```

設定が終わったらエディタを終了します．
(nanoの場合，Ctrl+oでファイルを保存し，Ctrl+xで終了)

その後，mosquittoサービスを再起動します．

```bash:mosquittoの再起動
$sudo systemctl restart mosquitto
```

#Webサーバーのインストール

次に，Webサーバ(apache2)をインストールします．
(taskselでLAMP serverをインストールしてもいいです)

```bash:apache2のインストール
$sudo apt install apache2
```

OSインストール時に[Ubuntu_ufw]を選択した場合は，
この時点ではiptablesによって80番ポートが閉じられているので，
Webブラウザでサーバーのページを開こうとしても，何も表示されません．

#ポートの開放

OSインストール時に[Ubuntu_ufw]を選択した場合は，
iptables(ファイアーウォール)が有効になっていて，
かつufw(iptablesのかんたん管理ツール)もインストール済みです．

　参考：[ufwを使って簡単にファイアウォールを設定する](https://qiita.com/yoh-nak/items/46935af3c5c4036e93b3
)

以下のコマンドを打って，mosquitoとapache2のためのポートを開けておきましょう．

```bash:MQTT/MQTToverWebSocketとHTTP/HTTPSポートを開ける
$ sudo ufw allow 1883/tcp
$ sudo ufw allow 15675/tcp
$ sudo ufw allow "Apache Full"
```

MQTTに必要な1883番ポート，
MQTT over WebSocketに必要な15675番ポート，
Apacheに必要な80と443番ポートを指定しています．

`Apache Full`という名前指定は，

```bash:登録済みポート名の確認方法
$ sudo ufw app list
```
というコマンドを打つと，予め登録されているネットワークサービスが表示されます．

```bash:listの表示結果
$ sudo ufw app list
Available applications:
  Apache
  Apache Full
  Apache Secure
  OpenSSH
```

　・`Apache`は80番ポート
　・`Apache Full`は80と443番ポート
　・`Apache Secure`は443番ポート
　・`OpenSSH`はsshの22番ポート
今回はhttpsは使いませんが，とりあえず`Apache Full`を使いました．


最後に，希望するポートが開いているかどうかを確認します．

```bash:ポートの状態確認方法
$ sudo ufw status
```
以下の様に表示されれば成功です．

```bash:statusの表示結果
$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
22                         ALLOW       Anywhere                  
1883/tcp                   ALLOW       Anywhere                  
15675/tcp                  ALLOW       Anywhere                  
Apache Full                ALLOW       Anywhere                  
22 (v6)                    ALLOW       Anywhere (v6)             
1883/tcp (v6)              ALLOW       Anywhere (v6)             
15675/tcp (v6)             ALLOW       Anywhere (v6)             
Apache Full (v6)           ALLOW       Anywhere (v6)  
```

正しく設定できているのを完了したら，
Webブラウザでhttp://**<font color="red">(VPSサーバのIPアドレス)</font>**/を開いてみてください．

![vps_apache2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/6d4ab205-3751-26ab-ae1a-06e56e78cc5c.png)

ブラウザにこんなページが表示されれば，apache2のインストールもポートの開放も成功です．

#HTMLのフォルダにファイルを置く

Webサーバのホームディレクトリである/var/www/htmlへ必要なファイルをコピーする必要があります．

以下の表のように，必要なファイルが沢山あります．

| 用途 | ファイル名 |
|:----------------:|:------------------:|
| WebSocket | mqttws31.js |
| Leafletプラグイン | MovingMarker.js，leaflet.rotatedMarker.js，leaflet.contextmenu.css，leaflet.contextmenu.js|
| ドローンアイコン | quad_x-90.png |
| HTMLファイル本体 | [start.html](https://github.com/DCoJA/OpenUVTM/tree/master/chapter16) |

それぞれ個別にダウンロードしてコピーするのは大変なので，圧縮ファイルを用意しました．
以下のコマンドでダウンロードと解凍を行ってください．

```bash:wgetで圧縮ファイルを取得し，Webサーバーのフォルダに解凍する
$ cd ~
$ wget https://github.com/DCoJA/OpenUVTM/raw/master/openuvtm_set.tar.gz
$ sudo tar -xvf openuvtm_set.tar.gz -C /var/www/html/
```

`tar`コマンドは`-C`オプションで解凍先を指定できます．
また，`/var/www/html/`へのファイルコピーにはsudo権限が必要です．

#ドローン側プログラム(dronekit-sitl)

今回はローカルの母艦でdronekit-sitlを起動して，VPSサーバーへMQTTでPubします．

具体的には　
　　[MQTTでドローンにコマンドを送る](https://qiita.com/hsgucci/items/9633099281506e8ae45b#%E3%83%89%E3%83%AD%E3%83%BC%E3%83%B3%E5%81%B4%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0dronekit-sitl)
で作ったプログラムの改変です．


[ここ](https://raw.githubusercontent.com/DCoJA/OpenUVTM/master/chapter16/gui_sitl_pubsub_vps.py)　を右クリック-[名前を付けて保存]するか，
以下のコードをコピー＆ペーストしてファイルを作成してください．

```python:gui_sitl_pubsub_vps.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import time                         # ウェイト関数time.sleepを使うために必要
from subprocess import Popen        # subprocessの中から、Popenをインポート
from signal import signal, SIGINT   # Ctrl+C(SIGINT)の送出のために必要 

from dronekit import connect        # connectを使いたいのでインポート
from dronekit import VehicleMode    # VehicleModeも使いたいのでインポート
from dronekit import LocationGlobal, LocationGlobalRelative   # ウェイポイント移動に使いたいのでインポート

import Tkinter                      # GUIを作るライブラリ
import paho.mqtt.client as mqtt     # MQTT送受信
import json                         # JSON<-->辞書型の変換用


#==dronekit-sitl の起動情報================================
# example: 'dronekit-sitl copter --home=35.079624,136.905453,50.0,3.0 --instance 0'
sitl_frame          = 'copter'          # rover, plane, copterなどのビークルタイプ
sitl_home_latitude  = '35.894087'       # 緯度(度)  柏の葉キャンパス駅前ロータリー
sitl_home_longitude = '139.952447'      # 経度(度)
sitl_home_altitude  = '17.0'            # 高度(m)
sitl_home_direction = '0.0'             # 機首方位(度)
sitl_instance_num   = 0                 # 0〜

# コマンドライン入力したい文字列をリスト形式で作成
sitl_boot_list = ['dronekit-sitl',sitl_frame,
                  '--home=%s,%s,%s,%s' % (sitl_home_latitude,sitl_home_longitude,sitl_home_altitude,sitl_home_direction),
                  '--instance=%s'%(sitl_instance_num)]

#connection_stringの生成
connection_string = 'tcp:localhost:' + str(5760 + int(sitl_instance_num) * 10 ) # インスタンスが増えるとポート番号が10増える


#== MQTTの情報，Pub/Subするトピック =======================
#mqtt_server = 'IPアドレスをここに書く'	  # 適宜サーバーのIPアドレスを指定すること
mqtt_server = '160.16.229.145'	  # 筆者のテストサーバーのIPアドレス
mqtt_port = 1883

mqtt_pub_topic = 'drone/001'  # Publish用のトピック名を作成
mqtt_sub_topic = 'ctrl/001'   # Subscribe用のトピック名を作成

#== MQTTに送信するJSONのベースになる辞書 ===================
drone_info = {
			"status":{
				"isArmable":"false",
				"Arm":"false",
				"FlightMode":"false"
			},
			"position":{
				"latitude":"35.0000", 
				"longitude":"135.0000",
				"altitude":"20",
				"heading":"0"
			}
}

#== MQTTで受信するコマンドのベースになる辞書 ================
drone_command = {
	"IsChanged":"false",
	"command":"None",
	"d_lat":"0",
	"d_lon":"0",
	"d_alt":"0"
}

#== メイン関数 ============================================
def main(args):
	#==Tkinterのウィンドウを作る===============================
	root = Tkinter.Tk()	# ウィンドウ本体の作成
	root.title(u'Dronekit-SITL Monitor')	# ウィンドウタイトルバー
	root.geometry('400x550')		# ウィンドウサイズ

	# Status
	frame0 = Tkinter.Frame(root,pady=10)
	frame0.pack()
	Label_status = Tkinter.Label(frame0,font=("",11),text="Status:")
	Label_status.pack(side="left")
	EditBox_status = Tkinter.Entry(frame0,font=("",11),justify="center",width=15)
	EditBox_status.pack(side="left")

	# IsArmable
	frame1 = Tkinter.Frame(root,pady=10)
	frame1.pack()
	Label_armable = Tkinter.Label(frame1,font=("",11),text="IsArmable:")
	Label_armable.pack(side="left")
	EditBox_armable = Tkinter.Entry(frame1,font=("",11),justify="center",width=15)
	EditBox_armable.pack(side="left")

	# ARM/DISARM
	frame2 = Tkinter.Frame(root,pady=10)
	frame2.pack()
	Label_arm = Tkinter.Label(frame2,font=("",11),text="Armed:")
	Label_arm.pack(side="left")
	EditBox_arm = Tkinter.Entry(frame2,font=("",11),justify="center",width=15)
	EditBox_arm.pack(side="left")

	# フライトモード
	frame3 = Tkinter.Frame(root,pady=10)
	frame3.pack()
	Label_flightmode = Tkinter.Label(frame3,font=("",11),text="Flight mode:")
	Label_flightmode.pack(side="left")
	EditBox_flightmode = Tkinter.Entry(frame3,font=("",11),justify="center",width=15)
	EditBox_flightmode.pack(side="left")

	# 緯度
	frame5 = Tkinter.Frame(root,pady=10)
	frame5.pack()
	Label_lat = Tkinter.Label(frame5,font=("",11),text="Latitude:")
	Label_lat.pack(side="left")
	EditBox_lat = Tkinter.Entry(frame5,font=("",11),justify="center",width=15)
	EditBox_lat.pack(side="left")

	# 経度
	frame6 = Tkinter.Frame(root,pady=10)
	frame6.pack()
	Label_lon = Tkinter.Label(frame6,font=("",11),text="Longitude:")
	Label_lon.pack(side="left")
	EditBox_lon = Tkinter.Entry(frame6,font=("",11),justify="center",width=15)
	EditBox_lon.pack(side="left")

	# 高度
	frame7 = Tkinter.Frame(root,pady=10)
	frame7.pack()
	Label_alt = Tkinter.Label(frame7,font=("",11),text="Altitude:")
	Label_alt.pack(side="left")
	EditBox_alt = Tkinter.Entry(frame7,font=("",11),justify="center",width=15)
	EditBox_alt.pack(side="left")

	# 方位
	frame8 = Tkinter.Frame(root,pady=10)
	frame8.pack()
	Label_dir = Tkinter.Label(frame8,font=("",11),text="Bearing:")
	Label_dir.pack(side="left")
	EditBox_dir = Tkinter.Entry(frame8,font=("",11),justify="center",width=15)
	EditBox_dir.pack(side="left")

	# InstanceNumber
	frame4 = Tkinter.Frame(root,pady=10)
	frame4.pack()
	Label_num = Tkinter.Label(frame4,font=("",11),text="Instance Number:")
	Label_num.pack(side="left")
	EditBox_number = Tkinter.Entry(frame4,font=("",11),justify="center",width=15)
	EditBox_number.pack(side="left")

	# Pubトピック名
	frame9 = Tkinter.Frame(root,pady=10)
	frame9.pack()
	Label_pubtopic = Tkinter.Label(frame9,font=("",11),text="Publish Topic:")
	Label_pubtopic.pack(side="left")
	EditBox_pubtopic = Tkinter.Entry(frame9,font=("",11),justify="center",width=30)
	EditBox_pubtopic.pack(side="left")

	# Subトピック名
	frame10 = Tkinter.Frame(root,pady=10)
	frame10.pack()
	Label_subtopic = Tkinter.Label(frame10,font=("",11),text="Subscribe Topic:")
	Label_subtopic.pack(side="left")
	EditBox_subtopic = Tkinter.Entry(frame10,font=("",11),justify="center",width=30)
	EditBox_subtopic.pack(side="left")

	#==dronekit-sitlの起動====================================
	p = Popen(sitl_boot_list)   # サブプロセスの起動
	time.sleep(1)   # 起動完了のために1秒待つ

	#==フライトコントローラ(FC)へ接続==========================
	vehicle = connect( connection_string, wait_ready=True )    # 接続

	#==MQTTのSubscribe関数====================================
	def on_message(client, userdata, msg):
		recv_command = json.loads(msg.payload)

		# 受信メッセージをコマンド辞書にコピー、その際に変更フラグを付加
		drone_command["IsChanged"] = "true"		# 届いた際にtrueにし，コマンドを処理したらfalseにする
		drone_command["command"] = recv_command["command"]

		if drone_command["command"] == "GOTO":
			drone_command["d_lat"] = recv_command["d_lat"]
			drone_command["d_lon"] = recv_command["d_lon"]
			drone_command["d_alt"] = recv_command["d_alt"]

	#==MQTTの初期化===========================================
	client = mqtt.Client()                  # クラスのインスタンス(実体)の作成
	client.connect( mqtt_server, mqtt_port, 60 )   # 接続先は自分自身
	client.subscribe( mqtt_sub_topic )
	client.on_message = on_message
	client.loop_start()                     # 通信処理スタート

	#==1秒おきに画面表示を更新する関数=========================
	def redraw():
		# ステータス、Arming関連、フライトモード情報の更新
		EditBox_status.delete(0,Tkinter.END)     # 前の文字列を削除
		EditBox_status.insert(Tkinter.END, str(vehicle.system_status.state) )  # 新しい文字列を書き込む
		EditBox_armable.delete(0,Tkinter.END)
		EditBox_armable.insert(Tkinter.END, str(vehicle.is_armable) )
		EditBox_arm.delete(0,Tkinter.END)
		EditBox_arm.insert(Tkinter.END, str(vehicle.armed) )
		EditBox_flightmode.delete(0,Tkinter.END)
		EditBox_flightmode.insert(Tkinter.END, str(vehicle.mode.name) )

		# 緯度/経度/高度/方位の更新
		EditBox_lat.delete(0,Tkinter.END)
		EditBox_lat.insert(Tkinter.END, str(vehicle.location.global_frame.lat) )
		EditBox_lon.delete(0,Tkinter.END)
		EditBox_lon.insert(Tkinter.END, str(vehicle.location.global_frame.lon) )
		EditBox_alt.delete(0,Tkinter.END)
		EditBox_alt.insert(Tkinter.END, str(vehicle.location.global_frame.alt) )
		EditBox_dir.delete(0,Tkinter.END)
		EditBox_dir.insert(Tkinter.END, str(vehicle.heading) )

		# 起動インスタンス番号、MQTTのトピック名の更新
		EditBox_number.delete(0,Tkinter.END)
		EditBox_number.insert(Tkinter.END, str(sitl_instance_num) )
		EditBox_pubtopic.delete(0,Tkinter.END)
		EditBox_pubtopic.insert(Tkinter.END, mqtt_pub_topic )
		EditBox_subtopic.delete(0,Tkinter.END)
		EditBox_subtopic.insert(Tkinter.END, mqtt_sub_topic )

		#==Publishするデータを作る===============================
		drone_info["status"]["isArmable"] = str(vehicle.is_armable)                 # ARM可能か？
		drone_info["status"]["Arm"] = str(vehicle.armed)                            # ARM状態
		drone_info["status"]["FlightMode"] = str(vehicle.mode.name)                 # フライトモード
		drone_info["position"]["latitude"] = str(vehicle.location.global_frame.lat)	# 緯度
		drone_info["position"]["longitude"] = str(vehicle.location.global_frame.lon)# 経度
		drone_info["position"]["altitude"] = str(vehicle.location.global_frame.alt)	# 高度
		drone_info["position"]["heading"] = str(vehicle.heading)					# 方位

		#==MQTTの送信===========================================
		json_message = json.dumps( drone_info )		# 辞書型をJSON型に変換
		client.publish("drone/001", json_message )   # トピック名は以前と同じ"drone/001"

		# コマンドに対する処理
		if drone_command["IsChanged"] == "true":
			# GUIDEDコマンド
			if drone_command["command"] == "GUIDED":
				print("# Set GUIDED mode")
				vehicle.mode = VehicleMode("GUIDED")

			# RTLコマンド
			if drone_command["command"] == "RTL":
				print("# Set RTL mode")
				vehicle.mode = VehicleMode("RTL")

			# ARMコマンド
			if drone_command["command"] == "ARM":
				print("# Arming motors")
				vehicle.armed = True

			# DISARMコマンド
			if drone_command["command"] == "DISARM":
				print("# Disarming motors")
				vehicle.armed = False

			# TAKEOFFコマンド
			if drone_command["command"] == "TAKEOFF":
				print("# Take off!")
				aTargetAltitude = 20.0
				vehicle.simple_takeoff(aTargetAltitude)  # Take off to target altitude

			# LANDコマンド
			if drone_command["command"] == "LAND":
				print("# Set LAND mode...")
				vehicle.mode = VehicleMode("LAND")

			# GOTOコマンド
			if drone_command["command"] == "GOTO":
				print("# Set target position.")
				if drone_command["d_lat"] == "0":
					drone_command["d_lat"] = str( vehicle.location.global_frame.lat )	# 緯度
				if drone_command["d_lon"] == "0":
					drone_command["d_lon"] = str( vehicle.location.global_frame.lon )	# 経度
				if drone_command["d_alt"] == "0":
					drone_command["d_alt"] = str( vehicle.location.global_frame.alt )	# 高度
				point = LocationGlobalRelative(float(drone_command["d_lat"]), float(drone_command["d_lon"]), float(drone_command["d_alt"]) )
				vehicle.simple_goto(point, groundspeed=5)

			# コマンドは読み終えたので、フラグを倒す
			drone_command["IsChanged"] = "false"
			drone_command["command"] = "None"

		root.after(1000, redraw )    # 1秒後に自分自身を呼び出す

	#==Tkinterの時間実行の機能after関数を使う===================
	root.after(1000, redraw )  # 最初に1回だけは本文で呼び出す必要がある．

	#==Tkinterメインループ====================================
	# Xボタンを押すまでこの関数がブロックする
	root.mainloop()

	#==ここから終了処理========================================
	# MQTT終了
	client.loop_stop()

	# フライトコントローラとの接続を閉じる
	vehicle.close()

	 # サブプロセスにもSIGINT送信
	p.send_signal(SIGINT)
	p.communicate()
	time.sleep(1)   # 終了完了のために1秒待つ

	return 0

# このpyファイルがimportされたのではなく，scriptとして実行された時
if __name__ == '__main__':
	sys.exit(main(sys.argv))	# ここでmain関数を呼ぶ．argvはC言語と同様にコマンドライン引数
```

プログラムは端末から実行します．

$python gui_sitl_pubsub_vps.py

##プログラム解説

変更点は36行目のMQTTサーバーの指定だけです．
以前はここを`localhost`として自分自身を指定していました．
今回はVPSサーバーのIPアドレスを書きます．

参考までに，筆者のVPSサーバーのIPアドレスを書いておきましたが，
自分のサーバーに書き換えて使ってください．

```python:変更点(参考に筆者のサーバーIPアドレスを掲載)
#== MQTTの情報，Pub/Subするトピック =======================
#mqtt_server = 'IPアドレスをここに書く'	  # 適宜サーバーのIPアドレスを指定すること
mqtt_server = '160.16.229.145'	  # 筆者のテストサーバーのIPアドレス
mqtt_port = 1883
```

#動作確認

Webブラウザで，`http://(VPSサーバーのIPアドレス)/start.html`を開けば，実行結果を見ることができます．

参考までに，筆者のサーバーのアドレスを記載しておきます．
（常時ドローンが飛んでいるとは限りませんが）

http://160.16.229.145/start.html

![vps_result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/a4e7c018-5466-8bfd-1f68-05d86769866d.png)

今までと同様に，ARM/DISARM，Takeoff/Land，緯度経度指定移動ができることを確認しましょう．

#(注意)サーバーセキュリティについて

今回のサーバー設定は「とりあえずの入門」としてiptablesとufwを使いました．
しかしこれは最低限度のセキュリティ対策です．

とりあえず施すべき対策としては，以下が考えられます．

・sshのポートを22番から変更するべき
　　sshで22番を使うことがわかっているので，
　　ユーザー名`root`,`admin`,`ubuntu`とかでログイン試行してくる攻撃はよくあります．
　　したがって，とりあえず22番以外のポートに変更するか，
　　SSL/TLSの暗号化鍵を使う様に変更するべきです．

・MQTTのポートを1883番から変更するべき
　　sshと同様に，MQTTの1883番もIoTでよく使われるので，
　　1883番にゴミパケットを投げまくって落とす攻撃がよくあります．
　　mosquittoが使うポート番号も1883以外にしておくべきでしょう．

さらには，fail2banを使って連続攻撃を検知し，一時的にbanできるようにすると良いでしょう．

#おわりに

今回はVPSサーバーにドローン(シミュレータ)の情報を投げて操作できるようにしました．
次回は，実機をインターネット越しに操作してみます．