前回からの続きです。
今回はLoRaWANデバイスからネットワークサーバ(Fukuoka City LoRaWAN
では、「プラットフォーム」と呼ばれています)へ、データを送信(Uplink)するところまでやりたいと思います。
プラットフォームのアクティベーション、ログイン
運営事業者へ利用申請してから待つこと数日、LoRaWAN運用基盤を提供しているThingPark
から「[ThingPark Portal] Your ThingPark Account Details」というSubjectのメールが届きました。
メール内にAccount Activation
というリンクがあったので、そこをクリックしてパスワードを登録すればアクティベーション完了です。
ここで落とし穴が待ち受けており、アクティベーション後にThingParkのログインページにリダイレクトされますが、そこがプラットフォームへのログインページじゃない、ということです。(私は見事に引っ掛かりました・・・)
事前に送られてきている「ユーザマニュアル」に記載されているURLが、正しいプラットフォームのアドレスとなりますので、ブラウザのアドレス欄にそれを打ち込めばプラットフォームログインページが開きます。
ログインページでアクティベーションの際に設定したパスワードにより、プラットフォームにログインします。
- ThingParkStore
- Fukuoka City LoRaWANでは利用しません。
- Device Manager
- デバイスやアプリケーションサーバを設定、管理できます。
- Wireless Logger
- デバイスの通信状況や、送られてきたデータを確認できます。
Device Manager
ポータルページのDevice Manager
右下の矢印をクリックすると、別タブで以下のページが開きます。
左メニューの各項目から、アプリケーションサーバやデバイスを管理できます。
- Devices
- デバイスの管理が行えます。
- Connectivity plans
- 通信プランを確認できます。予め3つの通信プランが設定されており、追加/変更/削除はできません。
- AS routing profiles
- アプリケーションサーバへのルーティング方法を管理できます。
- Application servers
- アプリケーションサーバ情報の管理ができます。
- Settings
- 利用者(Subscriber)情報の確認、アラームの設定ができます。
アプリケーションサーバの登録
「ユーザマニュアル」に従い、以下の手順でアプリケーションサーバ情報を登録します。
(デバイスとプラットフォーム間の通信試験であれば、この手順は省略できます)
-
Name
に任意のアプリケーションサーバ名を入力、Type
はHTTP Application Server
を選択し、Create
ボタンをクリック
-
Content Type
にXML
またはJSON
を選択(今回はJSON
を選択)し、Add a route
にあるAdd
ボタンをクリック
-
Source ports
にポート番号(HTTP≒80、HTTPS≒443、*≒全てのポート)を入力、Routing strategy
にBlast
(複数サーバ指定時、同時送信)、Sequential
(複数サーバ指定時、順次送信)のいずれかを選択し、Add
ボタンをクリック
-
Destination
にアプリケーションサーバ側エンドポイント(https://~)を入力し、`Add`ボタンをクリック
※今回は予め準備しておいたAmazon API GatewayのURLを入力(API Gateway側の手順は次回以降に投稿)
ルーティング方法登録
センサーデータがアプリケーションサーバに送られる際の、ルーティング方法を登録します。
(デバイスとプラットフォーム間の通信試験であれば、この手順は省略できます)
デバイスの登録
以下の手順で、通信に利用するデバイス情報を登録します。
尚、今回はABPモードでの通信を想定していますので、OTAAモードで通信する際は設定項目が異なります。
⇒「ユーザマニュアル」参照
№ 項目 説明 ① Device Name 任意のデバイス名 ② Device activation ABP
、またはOTAA
を選択(今回はABP
)③ DevEUI Device EUI を入力 ④ DevAddr Device Address を入力 ⑤ NwkSKey データ通信用の共通鍵を入力 ⑥ Device profile 利用するデバイスを選択(リストに無い場合は「LoRaWAN 1.0 - class A - ETSI - Rx2_SF9」を選択) ⑦ Connectivity plan 実証実験利用申込時に申請したプランを選択 ⑧ Application server routing profile 作成済のルーティングプロファイルを選択 ⑨ Addボタン クリックすることで⑩のAppsKey欄に行が追加され、設定できるようになる ⑩ AppSkeys データ通信用の共通鍵を入力(注)
※注
AppSKeyが未登録であったり、間違った(デバイス側に登録されているものと異なる)キーを登録してしまうと、デバイスが送信しているデータとプラットフォームで受信したデータが違う、という事象が発生します。(データの暗号/復号で異なるキーを利用しているので、まあ当然の結果ですが)
実は私自身もこの事象で半日程悩んでおり、本実証実験を先行して取り組んでいるUeno氏に相談したところ、全く同じ事象でハマったらしく直ぐに解決することができました。
(Uenoさん、ありがとうございました!)
手動による通信試験
ここまでの手順でプラットフォーム側の準備ができましたので、以下の手順によりデバイス側のコンソールから手動で通信試験を行ってみます。
ラズパイにSSH接続し、更にMinicomを起動してモデムへSerial接続します。
$ sudo LANG=c minicom
Welcome to minicom 2.7
OPTIONS: I18n
Compiled on Apr 22 2017, 09:14:19.
Port /dev/ttyACM0, 16:17:29
Press CTRL-A Z for help on special keys
「UART受信タイムアウト機能」を無効化します。
AT+UART=Timeout,0
+INFO: Input timeout, start parse
+UART: TIMEOUT is disabled
NwkSKey、AppSKey、AppKeyをデバイスに設定します。
(NwkSKey)
AT+KEY=NWKSKEY,"2BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+KEY: NWKSKEY 2B XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
(AppSKey)
AT+KEY=APPSKEY,"2BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+KEY: APPSKEY 2B XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
(AppKey)
AT+KEY=APPKEY,"2BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+KEY: APPKEY 2B XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
通信効率を上げる為、ADR機能をONにします。
AT+ADR=ON
+ADR: ON
これで設定周りが整ったので、テキストデータ(Hello LoRaWAN!
)を送信してみます。
AT+CMSG="Hello LoRaWAN!"
+CMSG: Start LoRaWAN transaction
+CMSG: TX "Hello LoRaWAN!"
+CMSG: Wait ACK
+CMSG: ACK Received
+CMSG: RXWIN2, RSSI -122, SNR -9.5
+CMSG: Done
+CMSG: Wait ACK
返答から、数秒程度経った後に+CMSG: ACK Received
が返されましたので、どうやら無事プラットフォームに送信できたようです。
尚、送信コマンドにはテキストデータの他に16進(HEX)で送るコマンドも用意されており、それぞれにプラットフォームから返されるACKを確認/無視するコマンドも用意されています。
- MSG
- テキスト送信用、ACKを無視
- CMSG
- テキスト送信用、ACKを確認
- MSGHEX
- HEX送信用、ACKを無視
- CMSGHEX
- HEX送信用、ACKを確認
通信試験結果の確認
プラットフォームに再度ログインし、ポータルページにある「Wireless Logger」を開きます。
表内の2列目が上矢印(Uplink)で、且つ3列目がdata
と表示されている行が、デバイスからのデータ送信ログになります。
その行の1列目の[+]
をクリックして展開し、Data(hex)
に記載されているものが送信されたデータ(Payload)です。
また、デバイスからテキストデータを送信した場合でも、Wireless Logger上ではHEX表示されます。
ちなみにこのHEXデータ48656c6c6f204c6f526157414e21
をascii文字に変換すれば、Hello LoRaWAN!
になります。
Pythonスクリプトの作成
手動での通信試験がうまくいったので、いよいよPythonスクリプトから送信してみたいと思います。
基本的には手動でやったことを、今度はスクリプトからモデムへシリアル通信することになります。
まずは前回のLoRaWANモデム用モジュールRHF3M076.py
に手を加え、各Keyのsetterやgetter、Peyload送信メソッド、ロギング処理等を追加しました。
# !/usr/bin/env python3
# coding: utf-8
# ------------------------------------------------------------------------------
# LoRaWAN modem (RHF3M076) module for Fukuoka City LoRaWAN
# by Kaho Musen Holdings Co.,Ltd.
#
# Created: 2017.11.03
# Author: k.nagase (a) gooday.co.jp
# ------------------------------------------------------------------------------
import serial
import re
import time
from logging import getLogger
# Class for RHF3M076
class RHF3M076:
# Constructor
def __init__(self, port='/dev/ttyACM0', baud=115200, timeout=0.1):
self._port = port
self._baud = baud
self._timeout = timeout
self._crlf = '\r\n'
self._ptnDevAddr = r'\+ID: DevAddr, (([0-9A-Fa-f]{2}[:-]){3}[0-9A-Fa-f]{2})'
self._ptnDevEui = r'\+ID: DevEui, (([0-9A-Fa-f]{2}[:-]){7}[0-9A-Fa-f]{2})'
self._ptnAppEui = r'\+ID: AppEui, (([0-9A-Fa-f]{2}[:-]){7}[0-9A-Fa-f]{2})'
self._ptnKeyErr = r'\+KEY: ERROR\((.*)\)'
self._logger = getLogger(type(self).__name__)
self._open()
# Open the serial port
def _open(self):
self._modem = serial.Serial()
self._modem.port = self._port
self._modem.baudrate = self._baud
self._modem.timeout = self._timeout
try:
self._modem.open()
self._modem.reset_input_buffer()
return()
except Exception as e:
self._logger.error('Serial port open failed.')
raise(e)
# Send key type and key value
def _setKey(self, KeyType, KeyValue):
try:
if KeyValue:
cmd = 'AT+KEY=' + KeyType + ',"' + KeyValue + '"'
ret = self._write(cmd)
self._waitResponse()
line = self._read()
ret = re.match(self._ptnKeyErr, line)
if ret:
raise Exception(KeyType + ' is invalid. (' + ret.group(1) + ')')
else:
raise Exception(KeyType + ' is empty.')
return()
except Exception as e:
self._logger.error(e)
# Send to serial port
def _write(self, cmd):
try:
self._logger.info('SEND:' + cmd)
cmd = cmd + self._crlf
ret = self._modem.write(cmd.encode())
return(ret)
except Exception as e:
print('ERROR: Send to serial port failed.')
raise(e)
# Receive from serial port
def _read(self):
len = self._waitResponse()
ret = self._modem.readline().decode().replace(self._crlf, '')
self._logger.info('RECV:' + ret)
return(ret)
def _waitResponse(self):
while self._modem.inWaiting() == 0:
time.sleep(0.1)
return(self._modem.inWaiting())
# Send payload
def sendPayload(self, Payload):
cmd = 'AT+CMSG="' + Payload + '"'
self._write(cmd)
#time.sleep(2)
len = self._waitResponse()
ack = False
while True:
line = self._read()
if line == '+CMSG: Done': break
if line == '+CMSG: ACK Received':
ack = True
return(ack)
@property
def DevAddr(self):
return(self._DevAddr)
@property
def DevEui(self):
return(self._DevEui)
@property
def AppEui(self):
return(self._AppEui)
@property
def NwksKey(self):
return(self._NwksKey)
@property
def AppsKey(self):
return(self._AppsKey)
@property
def AppKey(self):
return(self._AppKey)
@property
def ADR(self):
return(self._ADR)
@NwksKey.setter
def NwksKey(self, NwksKey):
self._NwksKey = NwksKey
self._setKey('NWKSKEY', self._NwksKey)
@AppsKey.setter
def AppsKey(self, AppsKey):
self._AppsKey = AppsKey
self._setKey('APPSKEY', self._AppsKey)
@AppKey.setter
def AppKey(self, AppKey):
self._AppKey = AppKey
self._setKey('APPKEY', self._AppKey)
@ADR.setter
def ADR(self, state):
self._ADR = state
strState = 'ON' if self._ADR else 'OFF'
cmd = 'AT+ADR=' + strState
ret = self._write(cmd)
len = self._waitResponse()
self._read()
# Destructor
def __del__(self):
self._modem.close()
以下のように、テストスクリプトを書いてみます。
# !/usr/bin/env python3
# coding: utf-8
from RHF3M076 import RHF3M076
from logging import basicConfig, getLogger, DEBUG
def main():
basicConfig(level=DEBUG)
logger = getLogger(__name__)
modem = RHF3M076()
modem.NwksKey = '2BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
modem.AppsKey = '2BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
modem.AppKey = '2BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
modem.ADR = True
if not modem.sendPayload('Hello LoRaWAN!'):
logger.error('Payload send failed.')
modem = None
return()
if __name__ == '__main__':
main()
Pythonから通信試験
テストスクリプトを実行し、送信試験を行ってみます。
$ ./main.py
INFO:RHF3M076:SEND:AT+KEY=NWKSKEY,"2BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
INFO:RHF3M076:RECV:+KEY: NWKSKEY 2B XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
INFO:RHF3M076:SEND:AT+KEY=APPSKEY,"2BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
INFO:RHF3M076:RECV:+KEY: APPSKEY 2B XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
INFO:RHF3M076:SEND:AT+KEY=APPKEY,"2BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
INFO:RHF3M076:RECV:+KEY: APPKEY 2B XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
INFO:RHF3M076:SEND:AT+ADR=ON
INFO:RHF3M076:RECV:+ADR: ON
INFO:RHF3M076:SEND:AT+CMSG="Hello LoRaWAN!"
INFO:RHF3M076:RECV:+CMSG: Start LoRaWAN transaction
INFO:RHF3M076:RECV:+CMSG: TX "Hello LoRaWAN!"
INFO:RHF3M076:RECV:+CMSG: Wait ACK
INFO:RHF3M076:RECV:+CMSG: ACK Received
INFO:RHF3M076:RECV:+CMSG: RXWIN1, RSSI -116, SNR -5
INFO:RHF3M076:RECV:+CMSG: Done
ACKも返ってきており、問題なく送信できているようです。
この後は、手動通信試験の時と同様にプラットフォーム側の「Wireless Logger」で、送信データを確認することができました。