Private LoRa通信を利用したIoTシステムを実装する〜データ格納&Web表示編〜
はじめに
Private LoRa通信を利用したIoTシステム実装の続きです.
前回の記事では,エッジデバイスからゲートウェイに集約したデータをデータベースに格納するパターンとしてApacheとMySQLを利用した手法を説明しました.
今回の記事では,集めたセンサデータをWebアプリケーション上でグラフにして表示するところまでをやってみます.今回はWebアプリケーションとしてDjangoを利用したアプリケーションを開発して,このアプリケーションでPOSTデータの受け取りも行います.
IoTシステムを実装手順
以下が本記事でIoTシステムを構築する手順です.今回の記事では【パターン2】の部分を解説していきます.
-
【共通部分】
- 温湿度センサとAdafruit Metro M4とES920LR2でエッジデバイスを作成する
- エッジデバイスからPrivate LoRa通信ゲートウェイにデータを集約する
-
【パターン1】Apache + MySQLでセンサデータをデータベースに格納するPrivate LoRa通信ゲートウェイのEthernet通信設定を行うデータサーバがPrivate LoRa通信ゲートウェイから受け取ったデータをデータベースに格納する
-
【パターン2】Django + MySQLでセンサデータをデータベースに格納し,Webアプリケーション上に表示する
- Private LoRa通信ゲートウェイのEthernet通信設定を行う
- Django REST frameworkでセンサデータをPOSTできるようにする
- DjangoのWebアプリケーション上でセンサデータをプロットする
この【パターン2】では,Djangoと呼ばれるPythonを活用したWebフレームワークを用いてWebアプリケーションを開発します.
また,Private LoRa通信ゲートウェイからサーバにPOSTされるセンサデータはDjango REST frameworkでデータ取得用のAPIを実装して,データの格納を行えるようにします.
ゲートウェイのEthernet通信設定を行う
まず,ES920GWX2のEthernet通信設定を行い,Private LoRa通信ゲートウェイがサーバへ接続およびデータPOSTできるようにします.
ここは前回の記事と共通している部分が多いので,不要な説明は読み飛ばして頂いても大丈夫です.前回の記事との差分は,データのPOST先の設定が異なる点にありますので,そこにはご注意ください.
ターミナルエミュレータの準備
ES920GWX2のEthernet通信設定は,前回の記事のPrivate LoRa通信設定時と同様にターミナルエミュレータを利用して行います.前回触れた内容と重複しますので,必要があれば以下のトグルから説明を表示して,それを参考に準備を進めてください.
Windowsをお使いの方の場合
Windows環境ではターミナルエミュレータとして,「Teraterm」を使用します.以下の記事を参考にインストールをお願い致します.
インストールが完了したら,以下の手順でTeratermの設定を変更します.
- ES920GWX2とPCをUSBケーブルで接続します.
- ES920GWX2の電源をONにします.(このとき電源供給を行うアダプタは純正のものを使用するようにしてください)
- Teratermを起動します.
- 「シリアル」からES920LRGWが接続されているポートを選択します.
- メニューバーの「設定」→「端末」を選択します.
- 「改行コード」にある「送信(M)」を「CR+LF」に変更し,ローカルエコーにチェックを入れます.
- メニューバーの「設定」→「シリアルポート」を選択します.
- 「スピード(E)」を「115200」に変更します.
- Teraterm内でEnterキーを入力し,「>COM」と出力されれば完了です.
Macをお使いの方の場合
Mac環境ではターミナルエミュレータとして「Coolterm」を使用します.以下の記事からインストールをお願い致します.
インストールが完了したら,以下の手順でCooltermの設定を変更します.
- ES920GWX2とPCをUSBケーブルで接続します.
- ES920GWX2の電源をONにします.(このとき電源供給を行うアダプタは純正のものを使用するようにしてください)
- Cooltermを起動します.
- メニューバーの「Connection」→「Options...」を選択します.
- 「Serial Port」の「Port」をES920LRGWが接続されているポートに接続し,「Baudrate」を「115200」に変更します.
- 「Terminal」の「Enter Key Emulation」が「CR+LF」になっていることを確認し,「Local Echo」にチェックを入れます.
- 「Connect」をクリックします.
- Coolterm内でEnterキーを入力し,「>COM」と出力されれば完了です.
ES920GWX2の設定を変更する
ここから,ES920GWX2がサーバへアクセス可能とするための設定を行なっていきます.
まず,ES920GWX2をEthernet通信でネットワークに接続します.そのためにはES920GWX2のEthernetポートのIPアドレスやサブネットマスク,デフォルトゲートウェイを適切に設定する必要があります.
上記の設定はそれぞれ,「dhcp」,「ipaddr」,「subnet」,「gateway」コマンドで設定変更可能です.適切な設定値は実装環境によって異なりますので,ES920GWX2 取扱説明書を参考にしながら設定を行います.
筆者の場合は,このES920GWX2を大学のプライベートネットワーク内で利用しました.そのため,設定手順としてはまず,「mac?」コマンドでES920GWX2のMACアドレスを確認し,そのMACアドレスをプライベートネットワーク内に登録することでIPアドレスの割り当てを行いました.このとき,サブネットマスクとデフォルトゲートウェイの情報も得られました.最後にES920GWX2のDHCP設定をOFFにして,IPアドレスなどの登録情報を入力しました.
ゲートウェイをネットワークに接続できる設定にしたところで,次はデータのPOST先を決めましょう.
本記事で使用するサーバのIPアドレスを<ServerIP>
であるとします.そして,今回の記事では,ES920GWX2のPOST先設定を以下のように設定します.(この部分が前回の記事と異なります)
設定項目 | コマンド | 設定内容 |
---|---|---|
サーバ名の設定 | servern | <ServerIP> |
サーバディレクトリの設定 | serverd | /uplink/data_post/ |
サーバポート番号の設定 | serverp | 8000 |
サーバとの通信接続を確認する
前節のEthernet通信設定でちゃんとES920GWX2とサーバが接続されるようになったかを確認します.
本記事では,通信疎通の確認はサーバ側から行います.また今回の記事で利用しているサーバのOSは「Ubuntu Server 20.04 LTS」です.(CentOS等でも進め方は一緒だと思います)
まず,みなさんのPCとサーバを接続します.接続の方法は特に指定はありませんが,個人的にはSSH接続が手っ取り早いと思います.(サーバの22番ポートが開放されていない場合はSSH接続できない場合があるのでご注意ください)
$ ssh <ユーザ名>@<ServerIP>
サーバへのSSH接続が完了したら,ping
コマンドを利用してサーバとES920GWX2の通信疎通を確認しましょう.このとき,ES920GWX2のIPアドレスを入力する必要がありますが,このIPアドレスはES920GWX2に対して「ipaddr?」コマンドを入力することで確認することができます.(ES920GWX2 取扱説明書を参考に確認できます)
ES920GWX2に割り当てられているIPアドレスが確認できたら,SSH接続しているサーバ内で以下のコマンドを入力します.(このときES920GWX2のIPアドレスを<GatewayIP>
であるとしてます.)
$ ping <GatewayIP>
【接続できている場合のレスポンス例】
64 bytes from <GatewayIP>: icmp_seq=1 ttl=64 time=0.476 ms
64 bytes from <GatewayIP>: icmp_seq=2 ttl=64 time=0.255 ms
:
【接続できていない場合のレスポンス例】
From <ServerIP> icmp_seq=9 Destination Host Unreachable
From <ServerIP> icmp_seq=10 Destination Host Unreachable
:
ES920GWX2とサーバの接続確認ができたら,ゲートウェイのEthernet通信設定は完了です.
Webアプリケーションを開発する
ここから,Private LoRa通信ゲートウェイからのPOSTデータを受け取り,かつそのデータをグラフを表示できるようにするWebアプリケーションを開発します.
今回の記事では,集約したセンサデータをグラフ上にプロットするところまでを説明しますが,このDjangoのWebアプリケーションはデータ分析など,他にも様々な使い方ができるので,本記事を読んだあとに勉強するとIoTシステムで提供できる価値が広がると思います.
環境構築
まず,SSH接続でサーバへアクセスし,以下のコマンドで本記事のWebアプリケーションを開発するためのディレクトリを作成します.ディレクトリ作成場所は特に指定はありませんが,筆者は/home/<ユーザ名>/
のディレクトリ内で開発しました.
$ cd <任意のディレクトリ>
$ mkdir test_iot_system
次にDjangoのWebアプリケーションを開発するための仮想環境を構築します.仮想環境構築はこちらが参考になります.
$ cd test_iot_system
$ mkdir web_app_env
$ python3 -m venv web_app_env
仮想環境の構築が完了したら,この仮想環境に入ります.
$ source web_app_env/bin/activate
↓ こんな感じになります
(web_app_env)$
そして,この仮想環境にDjangoのWebアプリケーション時に必要なライブラリをインストールしていきます.必要なライブラリは複数あるのでrequirements.txt
にまとめておいて,一気にインストールするのが楽です.本記事ではライブラリの後にバージョンを指定していますが,これをなしにすると最新のバージョンがインストールされます.
Django==3.2.0
djangorestframework==3.12.3
(web_app_env)$ touch requirements.txt
(web_app_env)$ pip install --upgrade pip
(web_app_env)$ pip install -r requirements.txt
ここまでで,DjangoのWebアプリケーションを開発する環境の構築は完了です.
Djangoプロジェクトの作成
次はDjangoのプロジェクトを作成します.以下のコマンドでDjangoのプロジェクトを作成しましょう.
(web_app_env)$ mkdir web_app
(web_app_env)$ cd web_app
(web_app_env)$ django-admin startproject config .
すると以下のようなファイル構成になります.
.
├── config
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
Djangoを学習されたことがない方は上記のコマンドでDjangoプロジェクトの「設定ファイル」と「実行ファイル」が作成されたとザックリ理解して頂けると良いです.(本記事では,Djangoの詳しい説明は省きますね)
データ格納用アプリケーションを作成する
さて,上記までで作成したDjangoプロジェクトはまだアプリケーションが実装されておらず,特に機能は持っていない状況です.
この章ではPrivate LoRa通信ゲートウェイからPOSTされたデータを受け取るアプリケーションを実装します.
アプリケーション作成手順
まずは「uplink」アプリケーションを生成します.
(web_app_env)$ python manage.py startapp uplink
すると以下のような「uplink」ディレクトリとファイルが生成されます.
.
├── config (省略)
├── manage.py
└── uplink
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
アプリケーションが作成できたら,settings.py
ファイルのINSTALLED_APPS
にこのアプリケーションを追加します.ついでにREST Frameworkも追加しておきます.また,DjangoプロジェクトにアクセスできるようにALLOW_HOST
も編集しておきます.そして,Djangoプロジェクトで使用する言語を日本語にして,時刻も東京の時刻にしてもらいます.
- ALLOWED_HOSTS = []
+ ALLOWED_HOSTS = ['<ServerIP>']
INSTALLED_APPS = [
...
+ 'uplink.apps.UplinkConfig',
+ 'rest_framework',
...
]
- LANGUAGE_CODE = 'en-us'
- TIME_ZONE = 'UTC'
+ LANGUAGE_CODE = 'ja'
+ TIME_ZONE = 'Asia/Tokyo'
また,uplinkアプリケーションのURLを定義します.
- from django.urls import path
+ from django.urls import path, include
urlpatterns = [
…
+ path('uplink/', include('uplink.urls')),
…
]
次に,ゲートウェイからPOSTされる温湿度センサデータのモデルを定義します.client_ipaddr
に関しては必須ではありませんが,POSTしてくるゲートウェイのIPアドレスがわかれば便利な場合(一つのIoTシステムで複数のゲートウェイを設置する場合など)もあるので,今回の記事では追加しておきます.
from django.db import models
from django.utils import timezone
from django.core.validators import MinLengthValidator
class TempHmdData(models.Model):
"""
温湿度センサ値のデータモデルクラス
create_at: データPOST時刻
temp: 温度データ
hmd: 湿度データ
private_lora_ownid: Private LoRa通信のOWNID設定
client_ipaddr: データをPOSTしたクライアントのIPアドレス
"""
class Meta:
db_table="temp_hmd_data"
verbose_name='TempHmdData'
verbose_name_plural='TempHmdData'
create_at = models.DateTimeField(
verbose_name='create_at',
blank=False,
null=False,
editable=False,
default=timezone.now,
)
temp = models.DecimalField(
verbose_name='temp',
max_digits=5,
decimal_places=2,
blank=False,
null=True,
default='00.00',
)
hmd = models.DecimalField(
verbose_name='hmd',
max_digits=5,
decimal_places=2,
blank=False,
null=True,
default='00.00',
)
private_lora_ownid = models.CharField(
verbose_name='OWNID',
max_length=4,
validators=[MinLengthValidator(4)],
blank=False,
null=False,
default='FFFF',
help_text='入力必須項目です.4桁の16進数アドレスを入力してください.',
)
client_ipaddr = models.GenericIPAddressField(
verbose_name='Client IP address',
blank=False,
null=False,
default='0.0.0.0',
editable=False,
)
さらに,adminファイルに上記のモデルを追加します
from django.contrib import admin
from .models import TempHmdData
@admin.register(TempHmdData)
class TempHmdDataAdmin(admin.ModelAdmin):
list_display = (
'create_at',
'temp',
'hmd',
'private_lora_ownid',
'client_ipaddr',
)
次はPOSTデータを受け取るURLを定義します.urls.py
はアプリケーション生成時に作成されないファイルなので,ご自分で生成をお願いします.
from django.urls import path
from . import views
app_name = 'uplink'
urlpatterns = [
path('data_post/', views.CreateData.as_view(), name='data_post'),
]
次にシリアライザを定義します.このserializer.py
も自動で生成されないファイルなので,こちらもご自分で生成してください.
from rest_framework import serializers
from .models import TempHmdData
class TempHmdDataSerializer(serializers.ModelSerializer):
client_ipaddr = serializers.IPAddressField()
class Meta:
model = TempHmdData
fields = (
'__all__'
)
read_only_fields = ('create_at', 'client_ipaddr', )
そして,次はViewsを定義します.
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser
from django.utils import timezone
from .serializer import TempHmdDataSerializer
import urllib
class CreateData(APIView):
parser_classes = (MultiPartParser, FormParser, )
def post(self, request, format=None):
# POSTされたデータ
post_data = request.body.decode()
# POSTされたデータをdict型に変換
payload = urllib.parse.parse_qs(post_data.strip())
# POSTクライアント
client_ipaddr = request.META.get('REMOTE_ADDR')
# POSTデータのペイロードをシリアライズするために整形
data = {
'create_at': timezone.localtime(timezone.now()),
'temp': float(int(payload['temp'][0])/100),
'hmd': float(int(payload['hmd'][0])/100),
'private_lora_ownid': str(payload['id'][0]),
'client_ipaddr': client_ipaddr,
}
# シリアライザにかける
serializer = TempHmdDataSerializer(data=data)
# シラアライズの成否で処理を変更
if serializer.is_valid():
serializer.save()
print('●Valied data post.')
print(serializer.validated_data)
return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
# エラーを吐いたら詳細を見せてもらう
print('●Data post is invalied.')
print(serializer.errors)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
最後にデータベースのマイグレーションをします.
(web_app_env)$ python manage.py makemigrations
(web_app_env)$ python manage.py migrate
データ格納確認
データ格納用アプリケーションuplinkが作成できたので,ちゃんとデータがPOSTされるかを確認しましょう.
データPOSTの確認はDjangoのAdminページで行いますので,そこにアクセスできるスーパーユーザアカウントを作っておきます.
(web_app_env)$ python manage.py createsuperuser
そして,Djangoプロジェクトを実行します.
(web_app_env)$ python manage.py runserver 0.0.0.0:8000
上記のコマンドを実行して●Valid data post.
と表示されるようになれば,データの格納に成功しています.
また,<ServerIP>:8000/admin
にアクセスし,admin画面のTempHmdDataを選択することでPOSTされたデータを確認することもできます.
※こちらの画像では取得したIPアドレスを隠していますが,取得可能です
グラフプロット用アプリケーションを作成する
エッジデバイスからの温湿度センサデータをデータベースに格納できたところで,次はWebアプリケーション上でグラフにしてプロットします.
アプリケーション作成手順
まずは「plot」アプリケーションを生成します.
(web_app_env)$ python manage.py startapp plot
生成できたら,plotアプリケーションを設定ファイルのINSTALLED_APPS
に追加し,urlpatterns
にURLの登録も行いましょう.ついでにテンプレートファイルをまとめた「template」ディレクトリを作成します.(個人的におすすめのファイル構成です)
+ import os
INSTALLED_APPS = [
...
+ 'plot.apps.PlotConfig',
...
]
TEMPLATES = [
{
...
- 'DIRS': [],
+ 'DIRS': [os.path.join(BASE_DIR, 'templates')],
...
},
]
(web_app_env)$ mkdir templates
↓ ザックリこういうファイル構成になります
.
├── config
├── db.sqlite3
├── manage.py
├── plot
├── templates
└── uplink
urlpatterns = [
…
+ path('plot/', include('plot.urls')),
…
]
そして,グラフをプロットするURLを定義します.このファイルは先ほどと同様ご自分での生成が必要です.
from django.urls import path
from . import views
app_name = 'plot'
urlpatterns = [
path('temp_hmd_data/<str:ownid>', views.PlotTempHmdData.as_view(), name='plot_temp_hmd_data'),
]
また,グラフをプロットするビューを定義します.
from django.shortcuts import render
from django.views.generic import ListView
from uplink.models import TempHmdData
from datetime import datetime
class PlotTempHmdData(ListView):
template_name = 'plot/temp_hmd_data.html'
model = TempHmdData
def get_queryset(self, **kwargs):
"""
TempHmdDataモデルデータから最新の20個を抽出する
"""
ownid = self.kwargs.get('ownid', '0000')
queryset = super().get_queryset(**kwargs).filter(private_lora_ownid=ownid).order_by('-create_at')[:20]
queryset = [
{
'create_at': data.create_at.strftime('%H:%M:%S'),
'temp': float(data.temp),
'hmd': float(data.hmd)
} for data in queryset
]
queryset.reverse()
return queryset
次に,グラフをプロットするテンプレート(HTMLファイル)を定義します.
<!DOCTYPE html>
<html>
<head>
<title>温湿度プロット</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
</head>
<body>
<canvas id="temp_chart"></canvas>
<canvas id="hmd_chart"></canvas>
</body>
<script>
var temp_ctx = document.getElementById('temp_chart');
var hmd_ctx = document.getElementById('hmd_chart');
var queryset = {{ object_list | safe }};
var labels = [];
var temp_data = [];
var hmd_data = [];
for (var i = 0; i < queryset.length; i++) {
labels.push(queryset[i]['create_at']);
temp_data.push(queryset[i]['temp']);
hmd_data.push(queryset[i]['hmd']);
}
console.log(labels);
console.log(temp_data);
console.log(hmd_data);
var plot_temp_data = {
labels: labels,
datasets: [
{
label: '温度データ',
data: temp_data,
borderColor: 'rgba(100, 255, 100, 1)',
fill: false,
},
]
};
var plot_hmd_data = {
labels: labels,
datasets: [
{
label: '湿度データ',
data: hmd_data,
borderColor: 'rgba(100, 100, 255, 1)',
fill: false,
},
]
};
var options = {};
var temp_chart = new Chart(temp_ctx, {
type: 'line',
data: plot_temp_data,
options: options
});
var hmd_chart = new Chart(hmd_ctx, {
type: 'line',
data: plot_hmd_data,
options: options
});
</script>
</html>
ここまででプロット用のアプリケーションが完成しました.
アプリケーションが完成したので,Djangoプロジェクトを実行します.
(web_app_env)$ python manage.py runserver 0.0.0.0:8000
<ServerIP>:8000/plot/temp_hmd_data/1000
にアクセスすると温度データと湿度データのそれぞれがWebアプリケーション上にプロットされてますね!
温度データプロット例
湿度データプロット例
以上で,今回の記事の目的は達成です.
みなさんお疲れ様でした!
おわりに
今回までの記事で,みなさんは以下のIoTシステムのフローを実装することができました.
- マイコンを使って温湿度センサからセンサデータを取得する
- 取得したセンサデータをPriate LoRa通信を使って発信し,ゲートウェイに集約する
- ゲートウェイに集約したデータをデータベースに格納する
- データベースに格納されたデータをWebアプリケーション上にプロットする
以上のシステムを開発したみなさんは簡単なIoTシステムを実装できるようになったと思います.
今回のIoTシステムはあくまで「センサデータをプロットすること」のみを目的として実装しましたが,本来のIoTシステムであれば,データベースに格納されたデータを分析し,ユーザにとって価値のある情報に変換してWebアプリケーション上に表示するなどの応用が必要となります.
今回の記事が,ユーザに届けるためのIoTシステムを開発するみなさんの助けになれば幸いです.
また,みなさんの貴重なご意見,ご感想をお待ちしております.
参考
【ゼロからわかる】Teratermのインストールと使い方
CoolTerm ダウンロードしてインストールします | Mac
ES920GWX2 取扱説明書
venv: Python 仮想環境管理 - Qiita
【Django】REST frameworkでプロジェクトにデータPOSTしてくるクライアントのIPアドレスをDBに格納する - Qiita
Chart.js を使って作る折れ線グラフのシンプルなサンプル
Private LoRa通信関連の記事シリーズ
Private LoRa通信モジュールに関する記事をいくつか書いておりますので,興味がございましたら合わせてご覧ください.
Discussion