Python
RaspberryPi
LoRaWan

Raspberry Pi+PythonでLoRaWAN Deviceにチャレンジしてみる(1)

More than 1 year has passed since last update.

はじめに

某所より、「今度Fukuoka City LoRaWANってゆう実証実験ば福岡市内を中心にやりますけん、おたくも都合ば付けてやらんですか?(注:筆者脳内訳)」というお誘いが当社にあったそうで、ふたつ返事で絶賛参加することになったようです。

そこで部署内の誰がこの件を担当するか、という事になり、「ローラ?なにそれ美味しいの?もしかして『オッケー』が口癖の女性タレント!?」状態で予備知識ゼロな私に、何故かRaspberry Piを触ったことがあるというだけで、白羽の矢が立ってしまいました。

という訳(?)で、ラズパイとLoRaWANモデムを繋いでPythonから通信し、既存DWHへデータを格納できるかチャレンジしたいと思います。

まず今回は、Raspberry PiとLoRaWAN Modemを接続し、最初はシリアルコンソール越しに、次にPythonプログラムからシリアル通信できるか確認したいと思います。

最終目標的なトポロジ

下図のようにAWSを利用することで、アプリケーションサーバを構成したいと思います。
Topology.png

用意したもの

  • Raspberry Pi 3 Model B (Complete Starter Kit
  • LoRaWAN Modem (RigingHF社製RHF3M076) ※運営事業者(NTTネオメイト様)からお借りしました。

開発環境

  • Raspbian GNU/Linux 9 (stretch)
  • Python 3.5.3

ラズパイとモデム接続

ラズパイのUSBポートにモデムを接続して、ラズパイを起動します。
※事前にネットワーク周りは設定済みです。
IMG_0582.JPG

シリアル通信できるようにする

Raspbianでは、デフォルトでSerialが無効化されていますので、有効化します。

$ sudo raspi-config

5 Interfacing Opsionsを選択
2017-10-24_17h34_41.png

P6 Serialを選択
2017-10-24_17h35_51.png

<はい>(または<Yes>)を選択
2017-10-24_17h36_39.png

<了解>(または<Ok>)を選択
2017-10-24_17h37_32.png

モデムを認識しているか確認

この時点で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

Serial port setupを選択
2017-10-25_10h50_40.png

A - Serial Device行のデバイス名を/dev/ttyACM0に変更
2017-10-25_10h52_37.png

Screen and keyboardを選択
2017-10-25_10h53_53.png

デフォルトではコンソールから入力したコマンドが表示されない(エコーバックしない)ため、ローカルエコーするよう変更
Q - Local echoYesに変更([Q]キーを押すごとにYesNoが切り替わる)
2017-10-25_10h54_56.png

Save setup as dflを選択(設定値をデフォルト値として保存)
2017-10-25_10h55_46.png

Exitを選択(Exit from minicomを選択すると、Minicom自体も終了)
2017-10-25_10h56_44.png

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)で改行コードを設定しなおす必要があります。

設定(S) - 端末(T)を選択
2017-10-25_11h28_54.png

受信、送信の改行コードを、下図のように設定
2017-10-25_11h30_46.png

コマンドを入力してみる

それではコンソールからAT(エンターキー)と入力してみます。

AT
+INFO: Input timeout, start parse
+AT: OK

何故か最後のエンターキーを押す前に、レスポンスが返ってきました。
他のATコマンドを試そうとしても、最初のAT2文字を打った時点で、必ずレスポンスが返ってきます。
また+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を取得する為だけの機能に限定したモジュールにしました。

RHF3M076.py
#!/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型でやり取りする必要がある為です。

テスト用スクリプトを作成し、動かしてみる

モデム用モジュールをテストするスクリプトを書きます。

main.py
#!/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