Help us understand the problem. What is going on with this article?

VPSで環境構築する

はじめに

このページは,

ドローン操作システムを作ろう

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

概要

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

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

しかし,

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

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

準備するもの

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

有名どころだと,以下の2つかと思います.

 参考:VPS比較ランキング2019完全版!これ見て選べば初心者でも安心

今回の記事は,
さくらのVPS CPU:仮想1Core メモリ:512MB SSD: 20GB」
のモデルで書きました.

VPSについて

簡単にVPSの説明を.

筆者個人の独断と偏見によるサーバーサービスのレベル分けを書いておきます.
Linuxの知識がどの程度必要か,Linuxガチ度で分類しています

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

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

3位 レンタルサーバー
    あるサーバーOS上のユーザアカウントを1つレンタル.
    PC自体は他のユーザーと共用.
    管理者権限は無いから,新しい機能のインストールとかはできない.
    予め準備された世界の中で遊ぶだけのエンドユーザ仕様.
    (準備された世界=お釈迦様の掌の上 と呼ぶ)
    使いみちはWebページとメールアカウントぐらい?

4位 クラウド
    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

が,トラフィックで従量課金というのは,
昔で言う「パケ死」が怖くて筆者はまだ試していません((((;゚Д゚))))
 参考:クラウド破産について

システム構成

システム構成は以下の図の様になります.
ドローンの代わりにシミュレータ(dronekit-sitl)を使っています.
vps_system.png

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

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

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

OSインストールを選択します.
さくらVPS_02_install.png

特別なOSは必要ないので,標準OSを選択します.
さくらVPS_03_standard.png

Ubuntu 18.04LTSはまだ無いので,16.04LTSにします.
さくらVPS_04_selectOS.png

初期ユーザー名はubuntuに固定です.
パスワードには,さくらインターネットから渡されたパスワードを使っておくと管理が楽です.
さくらVPS_05_password.png

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

[利用しない]を選択してもかまいませんが,グローバルIPを持ったサーバが丸裸状態になるのでハッキングに気をつける必要があります.
手動でファイアウォールをインストールすべきです.
さくらVPS_06_script.png

設定が完了したら[設定内容を確認する]ボタンを押し,
確認画面で[インストールを実行]を押してインストールを開始します.

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

また,さくらの管理画面にはシリアルコンソールがあるので,
ファイアーウォールのポート設定をミスった時などの緊急時にはこれを利用して管理することもできます.
さくらVPS_07_console.png

システムの更新

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

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

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

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

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

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

 MQTTブローカーの設定

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

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

nanoで編集する場合
$sudo nano /etc/mosquitto/mosquitto.conf

mosquitto.confファイルの先頭に,以下の行を追加します

listener 1883
listener 15675
protocol websockets

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

その後,mosquittoサービスを再起動します.

mosquittoの再起動
$sudo systemctl restart mosquitto

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

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

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

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

ポートの開放

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

 参考:ufwを使って簡単にファイアウォールを設定する

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

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という名前指定は,

登録済みポート名の確認方法
$ sudo ufw app list

というコマンドを打つと,予め登録されているネットワークサービスが表示されます.

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を使いました.

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

ポートの状態確認方法
$ sudo ufw status

以下の様に表示されれば成功です.

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://(VPSサーバのIPアドレス)/を開いてみてください.

vps_apache2.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

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

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でドローンにコマンドを送る
で作ったプログラムの改変です.

ここ を右クリック-[名前を付けて保存]するか,
以下のコードをコピー&ペーストしてファイルを作成してください.

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アドレスを書いておきましたが,
自分のサーバーに書き換えて使ってください.

変更点(参考に筆者のサーバー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

今までと同様に,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サーバーにドローン(シミュレータ)の情報を投げて操作できるようにしました.
次回は,実機をインターネット越しに操作してみます.

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away