#はじめに
某所より、「今度Fukuoka City LoRaWAN
ってゆう実証実験ば福岡市内を中心にやりますけん、おたくも都合ば付けてやらんですか?(注:筆者脳内訳)」というお誘いが当社にあったそうで、ふたつ返事で絶賛参加することになったようです。
そこで部署内の誰がこの件を担当するか、という事になり、「ローラ?なにそれ美味しいの?もしかして『オッケー』が口癖の女性タレント!?」状態で予備知識ゼロな私に、何故かRaspberry Piを触ったことがあるというだけで、白羽の矢が立ってしまいました。
という訳(?)で、ラズパイとLoRaWANモデムを繋いでPythonから通信し、既存DWHへデータを格納できるかチャレンジしたいと思います。
まず今回は、Raspberry PiとLoRaWAN Modemを接続し、最初はシリアルコンソール越しに、次にPythonプログラムからシリアル通信できるか確認したいと思います。
#最終目標的なトポロジ
下図のようにAWSを利用することで、アプリケーションサーバを構成したいと思います。
#用意したもの
- Raspberry Pi 3 Model B (Complete Starter Kit)
- LoRaWAN Modem (RigingHF社製RHF3M076) ※運営事業者(NTTネオメイト様)からお借りしました。
#開発環境
- Raspbian GNU/Linux 9 (stretch)
- Python 3.5.3
#ラズパイとモデム接続
ラズパイのUSBポートにモデムを接続して、ラズパイを起動します。
※事前にネットワーク周りは設定済みです。
#シリアル通信できるようにする
Raspbianでは、デフォルトでSerial
が無効化されていますので、有効化します。
$ sudo raspi-config
#モデムを認識しているか確認
この時点でOSがモデムを認識しているか確認してみます。
モデムを接続していない場合
$ lsusb
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
モデムを接続している場合
$ lsusb
Bus 001 Device 005: ID 0483:5740 STMicroelectronics STM32F407
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
どうやらRHF3M076
は、OS上ではSTM32F407
というProduct名で認識されるようです。
恐らくSTマイクロ社製の同チップが乗ってるのでしょう。
更にSerial通信用のポート(デバイスファイル)が出来ているか確認してみます。
$ ls -l /dev/tty*
・・・
crw-rw---- 1 root dialout 166, 0 10月 23 11:22 /dev/ttyACM0
・・・
モデムを外した場合、上記デバイスが消えるので、この /dev/ttyACM0
で間違いなさそうです。
#モデムにシリアル端末から接続してみる
シリアル端末プログラムの準備
CUI環境でシリアルコンソールを使いたいので Minicom
をインストールします。
$ sudo apt-get install minicom
インストールが完了したら初期設定します。
$ sudo LANG=c minicom -s
A - Serial Device
行のデバイス名を/dev/ttyACM0
に変更
デフォルトではコンソールから入力したコマンドが表示されない(エコーバックしない)ため、ローカルエコーするよう変更
Q - Local echo
をYes
に変更([Q]キーを押すごとにYes
、No
が切り替わる)
Save setup as dfl
を選択(設定値をデフォルト値として保存)
Exit
を選択(Exit from minicom
を選択すると、Minicom自体も終了)
Exit
を選択し、以下の表示に切り替わればモデムへコンソール接続された状態となります。
Welcome to minicom 2.7
OPTIONS: I18n
Compiled on Apr 22 2017, 09:14:19.
Port /dev/ttyACM0, 14:33:04
Press CTRL-A Z for help on special keys
ちなみにコンソール接続中、[ctrl]+[A]、[Z]キーでコマンドサマリが表示され、更に[Q]キーを押すとMinicomを終了(コンソール切断)することができます。
また再度コンソール接続するには、次のコマンドを入力します。
$ sudo LANG=c minicom
#ATコマンドで操作してみる
##改行コードの変更(TeraTermの場合)
RHF3M076では、ATコマンドの終端には必ず改行コード(<LF>(\n)
、または<CR><LF>(\r\n)
)が必要です。
※ちなみにモデムからのレスポンスメッセージには、行終端に<CR><LF>(\r\n)
が付いていました。
そこでモデムをATコマンドで操作する前に、Raspberry Pi にSSH接続しているターミナル(今回はTeraTerm)で改行コードを設定しなおす必要があります。
##コマンドを入力してみる
それではコンソールからAT
(エンターキー)と入力してみます。
AT
+INFO: Input timeout, start parse
+AT: OK
何故か最後のエンターキーを押す前に、レスポンスが返ってきました。
他のATコマンドを試そうとしても、最初のAT
2文字を打った時点で、必ずレスポンスが返ってきます。
また+AT: OK
メッセージの前に、+INFO: Input timeout, start parse
というメッセージが返ってきていますので、直感的にこれが原因だろうと推測しました。
そこで色々調べたところ、これは「UART受信タイムアウト機能」というものらしく、モデムがAT
文字を受信した直後から予め設定されたタイムアウト値(ミリ秒)に達すると、強制的に受信終了する機能だそうです。
(このタイムアウト設定値はAT+UART=TIMEOUT
コマンドで確認できます。)
このままだと手動でATコマンドを試すことができないので、この機能を一時的に無効にするために次のATコマンドを入力します。
但し、コンソールから直接入力すると上記の二の舞になりますので、まずはエディタ等でコマンドを入力し、それをコンソールにコピペすることにします。
AT+UART=Timeout,0
うまくタイムアウト機能を無効化できれば、次のようなレスポンスが返ってきます。
+INFO: Input timeout, start parse
+UART: TIMEOUT is disabled
これで、ATコマンドで設定や設定値を確認できるようになりました。
試しにモデムに登録されているID(DevAddr、DevEui、AppEui)を確認してみます。
AT+ID
+ID: DevAddr, 01:23:45:67
+ID: DevEui, aa:bb:cc:dd:ee:ff:aa:bb
+ID: AppEui, ab:cd:ef:ab:cd:ef:ab:cd
尚、ATコマンドの詳しいリファレンスはここからダウンロードできます。
#Pythonからモデムと通信してみる
シリアル通信でモデムをATコマンドを使って操作できるようになりましたので、次はPythonからモデムとシリアル通信してみます。
##独自モジュール作成
pySerial
モジュールを利用して、モデムをATコマンドで操作できるようにする独自モジュールRHF3M076.py
を作成します。
尚、私の環境ではすでにpySerial
モジュールがインストールされていましたが、もしインストールされていない場合は次のコマンドでインストールしましょう。
$ sudo pip install pySerial
そして以下が実際に作成したRHF3M076.py
のコードです。
この時点では解りやすくするため、単にIDを取得する為だけの機能に限定したモジュールにしました。
#!/usr/bin/env python3
# coding: utf-8
import serial
import re
# Class of LoRaWAN Modem
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._open()
self._getId()
# Open serial port
def _open(self):
self._modem = serial.Serial(
port=self._port,
baudrate=self._baud,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=self._timeout,
xonxoff=False,
rtscts=False,
write_timeout=None,
dsrdtr=False,
inter_byte_timeout=None
)
return()
# Get IDs
def _getId(self):
cmd = 'AT+ID'
self._write(cmd)
lines = self._read()
print(lines)
for line in lines:
res = re.match(self._ptnDevAddr, line)
if res:
self._DevAddr = res.group(1)
continue
res = re.match(self._ptnDevEui, line)
if res:
self._DevEui = res.group(1)
continue
res = re.match(self._ptnAppEui, line)
if res:
self._AppEui = res.group(1)
# Send to serial port
def _write(self, cmd):
cmd = cmd + self._crlf
ret = self._modem.write(cmd.encode())
return(ret)
# Receive from serial port
def _read(self):
result = []
lines = self._modem.readlines()
for line in lines:
result.append(line.decode().replace(self._crlf, ''))
return(result)
@property
def DevAddr(self):
return(self._DevAddr)
@property
def DevEui(self):
return(self._DevEui)
@property
def AppEui(self):
return(self._AppEui)
# Destructor
def __del__(self):
self._modem.close()
尚、_read
、_write
メソッドの中で文字列をencode/decodeしていますが、これはプログラムからモデムと通信する場合、改行コードの関係上bytes型でやり取りする必要がある為です。
##テスト用スクリプトを作成し、動かしてみる
モデム用モジュールをテストするスクリプトを書きます。
#!/usr/bin/env python3
# coding: utf-8
from RHF3M076 import RHF3M076
def main():
modem = RHF3M076()
print('DevAddr: ' + modem.DevAddr)
print('DevEui: ' + modem.DevEui)
print('AppEui: ' + modem.AppEui)
modem = None
return()
if __name__ == '__main__':
main()
以下がテストスクリプトを実行してみた結果です。
Pythonとモデム間で、問題なく通信できているようです。
$ chmod +x main.py
$ ./main.py
DevAddr: 01:23:45:67
DevEui: aa:bb:cc:dd:ee:ff:aa:bb
AppEui: ab:cd:ef:ab:cd:ef:ab:cd