Raspberry Pi 3 と AWS でインターネット速度を計測・記録する

# はじめに
\_s\_\_o\_ です。

私事ですが、昨年マンションを引っ越しました。インターネット接続は、マンションの各戸に RJ-45 の口が付いているような、いわゆる「マンション共有型」タイプです ([こちら](https://tsunaga-ru.net/internet-mansion-40331) の分類でいうところの「インターネット完備マンション」にあたります)。

メリットは利用料が安いことです。一方、マンションに引き込んだ光回線を共有設備の SW で各戸に分配しているため、回線速度が他戸の利用状況にかなり左右されます。
※ちなみに、マンションに引き込む光回線も、大本を (最大で) 32 分岐しているそうです ([参考](https://simpc.jp/rikatech/kodate-shugo/) )。今回の引っ越しを機に知りました。恐ろしい。。。

その「左右され具合」がなかなかに大きいので、いっそのこと「見える化」(not 可視化 but 数値化) してやろうと思いました。というわけで、Raspberry Pi 3 と AWS を使って、ちょろっと環境を作っていきます。

# 構成イメージ
下記のイメージです。
![ラズバイからDynamoDBへ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46216/a7922963-0faa-33d3-1f19-d7dc003dfa38.png)

それぞれの役割は下記のとおりです。
- Raspberry Pi 3
  - Raspberry Pi 3 Model B で、中身は個人的な趣味で CentOS にしています。
  - Python 2.7 で「(1) インターネット速度計測」、「(2) AWS IoT へのデータ配信」を実現します。
- AWS IoT Core
  - Raspberry Pi 3 の配信データを Subscribe しています。
- DynamoDB
  - AWS IoT Core のルールにもとづき、IoT が受信したデータを保管します。

# 準備
## Dynamo DB
下記のようなテーブルを作成しておきます。
![dynamo_db.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46216/226ae072-cad6-3c0e-7661-380176838b55.png)

特記事項はありませんが、基本的なパラメータは下記としています。

- テーブル名 : internet_speed
- プライマリパーティションキー : client_id
→ クライアント名を格納します。クライアント名はこの後に登場する Python コードの中で定義しています。
- プライマリソートキー : timestamp_aws
→ 格納時のタイムスタンプ (unixtime) を格納します。タイムスタンプは後述の AWS IoT のルールの関数で出力します。

## AWS IoT
[こちら](https://symfoware.blog.fc2.com/blog-entry-2224.html) の記事を参照にしながら、エンドポイントを作成します。過程で証明書 & 秘密鍵が生成されるので、ダウンロードして Raspberry Pi 3 上に配置しておきます。

また、CA 証明書も必要になるので、[こちら](https://docs.aws.amazon.com/iot/latest/developerguide/server-authentication.html?icmpid=docs_iot_console#server-authentication-certs) からダウンロードし、上記と同様に Raspberry Pi 3 上に配置しておきます。

上記を実施後、DynamoDB に連携するため、下記のようにルールを設定します。
![IoT_ルール.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46216/c422358d-bc2f-54a3-ee34-11708dd14618.png)

SQL 部分をピックアップします。ポイントは下記の通りです。
- トピック名「internet_speed」から情報を取り出す。
- 「client_id」と「timestamp_aws」を関数で取得する。
- それ以外は微妙に名前を変えたりしながら取得する。

```
SELECT clientid() AS client_id, timestamp() AS timestamp_aws, unixtime As timestamp_device1, datetime AS timestamp_device2, down, up FROM 'internet_speed'
```

## Raspberry Pi 3 (Python 2.7)
「速度計測用」と「AWS IoT へのデータ配信」に 2 つのコードを準備します。また、それらのコードを呼び出すラッパースクリプトを準備します。

### 速度計測用コード (speed_test.py)
処理として、主に下記を行います。
1. Down 速度と Up 速度を計測する。
1. タイムスタンプを取得する。
1. 上記をまとめて json で出力する。

なお、「speedtest」というモジュールを使うため、事前に pip や easy_install でインストールする必要があります。インストール方法は [こちら](https://qiita.com/yokobonbon/items/67deb3fab84b0c0954e0) などを参照してください。

```
# -*- coding:utf8 -*-

# https://qiita.com/yokobonbon/items/67deb3fab84b0c0954e0
# https://qiita.com/hiroyuki827/items/3488a2b578de6777d2fc

import speedtest
import time
import datetime
import json

json_name = 'tmp.json'

def get_speed_test():
    servers = []
    stest = speedtest.Speedtest()
    stest.get_servers(servers)
    stest.get_best_server()
    return stest

def get_now():
    time_now = time.time()
    return time_now

def test_speed(stest):
    down_result = int(stest.download())
    up_result = int(stest.upload())
    return down_result, up_result

def command_line_runner():
    stest = get_speed_test()
    now_unixtime = get_now()
    now_datetime = datetime.datetime.fromtimestamp(now_unixtime)
    now_datetime_str = str(now_datetime.strftime('%Y/%m/%d %H:%M:%S'))
    down_result, up_result = test_speed(stest)

    f = open(json_name, 'w')
    result = {'unixtime': (now_unixtime * 1000), 'datetime': now_datetime_str, 'down': down_result, 'up': up_result}
    json.dump(result, f)
    f.close()

if __name__ == '__main__':
    command_line_runner()
```

### データ配信用コード (publish_aws.py)
処理として、主に下記を行います。
1. json ファイル (tmp.json) を AWS IoT に配信する。

なお、「AWS IoT Device SDK for Python」を使うため、事前に pip でインストールする必要があります。インストール方法は [こちら](https://symfoware.blog.fc2.com/blog-entry-2224.html) などを参照してください。

また、コード中の ca_cert_path・key_path・cert_path の部分は、(上述の手順で配置した) CA 証明書・サーバ証明書・サーバ秘密鍵のパスを指定してください。

```
# -*- coding:utf8 -*-

# https://symfoware.blog.fc2.com/blog-entry-2224.html

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import time
import json

json_name = 'tmp.json'

# AWS IoT Info
clientName = 'lcrpi01' # クライアント名。AWS IoT の ルールの SQL で参照する
endpoint = '*****'
ca_cert_path = '*****'
key_path = '*****'
cert_path = '*****'
topic_name = 'internet_speed' # トピック名。AWS IoT の ルールの SQL で参照する

def command_line_runner():
    # json load
    f = open(json_name, 'r')
    json_load = json.load(f)
    f.close()

    # For certificate based connection
    myMQTTClient = AWSIoTMQTTClient(clientName)
    myMQTTClient.configureEndpoint(endpoint, 8883)
    myMQTTClient.configureCredentials(ca_cert_path, key_path, cert_path)

    # Set MQTT Parameter
    myMQTTClient.configureOfflinePublishQueueing(-1) # Infinite offline Publish queueing
    myMQTTClient.configureDrainingFrequency(2) # Draining: 2 Hz
    myMQTTClient.configureConnectDisconnectTimeout(10) # 10 sec
    myMQTTClient.configureMQTTOperationTimeout(5) # 5 sec

    # Publish to AWS IoT
    myMQTTClient.connect()
    myMQTTClient.publish(topic_name, json.dumps(json_load), 1)

if __name__ == '__main__':
    command_line_runner()
```

### ラッパースクリプト (internet_speed.sh)
上記の Python コードを呼び出すだけの単純なスクリプトです。これを cron で定期的に回します (30 分に 1 回など)。

```
#!/usr/bin/bash
cd /home/snishiyama/aws_iot

# 1. Speed Test
python ./speed_test.py
sleep 1

# 2. Publish to AWS IoT
python ./publish_aws.py
sleep 1
```

# 取得結果
こんな感じで取得できます。今後の展開を考えて、できるだけ生データで取得しているので、視認性が低いのはご容赦を。
![取得結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46216/424bf519-157a-0bb8-3599-2fc897e4be61.png)

# おまけ
数値化とくれば次は可視化だと思います。AWS であれば、Elasticsearch Service & Kibana などが有名です……が、個人で利用するには些か高い (コストが)。

というわけで、簡単に可視化するため、Ambient というサービスとも組み合わせました (既に AWS は関係なくなってますが。。。)。

https://ambidata.io/

Ambient と連携するために、Raspberry Pi 3 に下記のコードを追加しています (publish_ambient.py)。例によって、事前に「ambient」モジュールを入れる必要があるため、[こちら](https://ambidata.io/samples/temphumid/python/) を参考にしながら導入しました。

```
# -*- coding:utf8 -*-

# https://ambidata.io/samples/temphumid/python/

import ambient
import json

json_name = 'tmp.json'

# Ambient Info
ambi_Channel_id = *****
ambi_write_key = '*****'

def convert_to_mbps(bps):
    mbps = float(bps)/1000/1000
    return mbps

def command_line_runner():
    # json load
    f = open(json_name, 'r')
    json_load = json.load(f)
    f.close()

    # Adjust the value
    down_mbps = convert_to_mbps(json_load['down'])
    up_mbps = convert_to_mbps(json_load['up'])

    # Publish to Ambient
    msg = {'d1' : down_mbps, 'd2' : up_mbps}
    ambi = ambient.Ambient(ambi_Channel_id, ambi_write_key)
    r = ambi.send(msg)
    return r

if __name__ == '__main__':
    command_line_runner()
```

Ambient で描画したのが下記グラフです。期待したイメージ通りです。
![ambient.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46216/f73d919b-97b3-c293-2d3f-037c32eccb62.png)

……激しい荒ぶり方ですね。。。

# まとめ
以上、Raspberry Pi 3 と AWS (IoT と DynamoDB) を使ってインターネットの回線速度を収集する方法でした。今まで IoT に触れたことがなかったため、こういった機会に経験できて良かったです。今後は他の項目を収集したり (せっかく Raspberry Pi を使っているので)、AWS の機能で何とか (低コストで) 可視化していきたいなあと思っています。

# 追記
その後、FTTH 開通しました。ちょうどタイミングが良く、ひいきにしているプロバイダが「IPoE 標準提供」を始めたため、期せずして IPoE デビューもできました。

肝心の Raspberry Pi 3 で計測したインターネット速度ですが……なぜか 100 Mbps で頭打ちです。他の端末 (PC やスマホ) で計測した場合は 300 Mbps を越えるのですが、なぜか Raspberry Pi 3 で計測した場合はそれが出てこない。明らかに原因は  Raspberry Pi 3 なので色々調べてみたところ……  Raspberry Pi 3 の NIC は 100 Mbps までにしか対応していないという、哀しいオチが判明しました。

仕方がないのでギガイーサの USB NIC を Raspberry Pi 3 につないで、それで計測するようにしました。結果は下記の通りです。
※ちなみに、Raspberry Pi 3 の USB は USB 2.0 なので、USB NIC がギガでも、理論上の MAX は 480 Mbps となります。
![インターネットスピード_FTTH後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46216/96a02937-f55a-4b1d-22c0-dfd06c33947b.png)

まあ、300 Mbps は出ませんが、少なくとも以前に比べて改善は見られますので、とりあえず OK ということで。