Private LoRa Gateway経由でnodeのデータをAWS IoTにpublish① E220でGateway構築

## この記事について

AWS IoTにデータ転送するためのPrivate LoRa GWとLoRa Deviceの構築手順を3回に分けて記録します。

まず本稿ではラズパイに

* LoRaHATを装着しPrivate LoRaデバイス化
* インターネット経由ではpahoを使いAWS IoTとMQTT

を実装してPrivate LoRa gatewayを構築します。下図の色付き部分です。

<br>

<img width= 700 src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565396/4752fd5f-b5e6-a038-34ef-f607d08d6ade.png>

<br>

昨年夏休みを溶かしてLoRaWANとPrivate LoRaを構築しましたが、Privateは没ネタとなったので記憶から消える前に備忘録を残したいと思います。



## デバイス

### RaspberryPi  

Raspberry Pi： Raspberry Pi 4 Model B Rev 1.2
OS： Raspberry Pi OS Lite 64-bit (Debian GNU/Linux 11 (bullseye))

### LoRaHAT

[Raspberry Pi 3,4用LoRa HAT（DTH-RPLR）](https://dragon-torch.tech/cat-components/extension-boards/dth-rplr/)

* [データシート](https://dragon-torch.tech/wp-content/uploads/2024/04/data_sheet_Rev1.6.pdf)
* [HAT用説明書・サンプルコード](https://dragon-torch.tech/wp-content/uploads/2022/08/E220-900T22SJP_LoRaHAT_Guide_Rev1.zip)

zipのサンプルコードにはconfig_codeとoperation_codeの二つのdirectoryが入っていますのでラズパイの/home/user/にコピーして使います。

> 型名	DTH-RPLR  
RFモジュール	LoRa通信モジュール（E220-900T22S（JP））
通信モジュールの仕様はこちら＞＞
対応ボード	Raspberry Pi 3,4
インターフェース	UART
モード制御I/O (信号レベルは3.3V TTL)
電源電圧	動作定格　3.3V ～ 5.5V
推奨電圧範囲　3.5V ～ 5.0V
動作温度範囲	-45℃ ～ +85℃
外形寸法	56mm x 65mm

リンク先の画像です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565396/981f3ddd-b67a-319d-223d-fbf59be0f6ca.png)

### LoRa用アンテナ

[TX915-JKS-20](https://www.switch-science.com/products/8676?_pos=2&_sid=88851d8ff&_ss=r)

## LoRa Gateway

### ラズパイ基本環境

[過去記事](https://qiita.com/Saito5656/items/62e46f009000991b2adf#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)の環境から必要に応じて追加/削除します。

wiringpiがよろしくないですが今回はこのまま使います。

```sh:追加の操作
#シリアルポート有効化
sudo raspi-config nonint do_serial 2
#ライブラリ追加
pip install pyserial wiringpi
```

### AWS IoTリソース

[過去記事](https://qiita.com/Saito5656/items/62e46f009000991b2adf#aws%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E6%A7%8B%E7%AF%89)同様に証明書、Thing、policyを作成して証明書はラズパイの/home/user/certに保存します。

### LoRa通信parameters

zipに含まれるE220-900T22S(JP)_シンプルLoRaHAT_利用ガイド_Rev1.pdfに従い以下の設定にします。暗号化はありにします。

```sh:/home/user/config_code/setting.ini
[E220-900JP]
own_address=0
baud_rate=9600
bw=125
sf=9
subpacket_size=200
rssi_ambient_noise_flag=0
transmitting_power=13
own_channel=0
rssi_byte_flag=1
transmission_method_type=2
wor_cycle=3000
encryption_key=1234
```

### LoRa Gateway + MQTT broker

/home/user/operation_code/receive.pyに手を加えて、
* E220デバイス間でのPrivate LoRa通信をsubする  
* subしたpayloadをAWS IoT Coreにpubする　　

LoRa Gateway + MQTT brokerにしました。

```python:gateway.py
import serial
import time
import argparse
import hexdump
import paho.mqtt.client as mqtt
import json
import ssl
import sys
import os

#####################################################################
# MQTT/aws IoT core settings
#####################################################################
HOSTNAME = os.uname()[1]
# AWS IoT core
host = 'xxxxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com'  # AWS IoT Endpoint
port = 8883  # port
cacert = './cert/AmazonRootCA1.pem'  # root ca
clientCert = './cert/privateLoRaAP1-certificate.pem.crt'  # certificate
clientKey = './cert/privateLoRaAP1-private.pem.key'  # private key
clientid = HOSTNAME
# MQTT base topic settings
topic = HOSTNAME  # topic
#####################################################################

#publisherを立てる際のコールバック関数
def on_connect(client, userdata, flags, respons_code):
    print('Connected')

#MQTTクライアントを起動してオブジェクト作成する関数
def aws_connect():
    try:
        client = mqtt.Client(client_id=clientid, protocol=mqtt.MQTTv311)
        # certifications
        client.tls_set(
            cacert,
            certfile=clientCert,
            keyfile=clientKey,
            tls_version=ssl.PROTOCOL_TLSv1_2)
        client.tls_insecure_set(True)
        # callback
        client.on_connect = on_connect
        # port, keepalive
        client.connect(host, port=port, keepalive=60)

        client.loop_start()
    except KeyboardInterrupt:
        sys.exit()
    return client

def publish(client, topic, body): 
    client.publish(topic, json.dumps(json.loads(body), default=json_serial), qos=1) # publish
    print(f"JSON body Published to AWS IoT with topic:{topic}")
    return

def json_serial(obj):
    return obj.isoformat()

def get_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("serial_port")
    parser.add_argument("-b", "--baud", default="9600")
    parser.add_argument("-m", "--model", default="E220-900JP")
    parser.add_argument("--rssi", action="store_true")
    args = parser.parse_args()

    return args


def main():

    client = aws_connect()

    args = get_args()

    if args.model == "E220-900JP":
        print(f"serial port: {args.serial_port}")
        print("receive waiting...")
        with serial.Serial(args.serial_port, int(args.baud), timeout=None) as ser:
            payload = bytes()
            print("payload:", payload)
            while True:
                if ser.in_waiting != 0:
                    payload = payload + ser.read()
                elif ser.in_waiting == 0 and len(payload) != 0:
                    time.sleep(0.010)
                    if ser.in_waiting == 0:
                        body = {}
                        body['Timestamp'] = int(time.time())
                        print("payload:", payload)
                        payload_str = payload.decode(encoding='utf-8', errors='replace')
                        dev_no = payload_str.split("\n")[1]
                        payload_str = payload_str.split("\n")[0]
                        print("dev No.: ", dev_no)
                        topicx = topic + "/LoRaDev" + dev_no
                        print("topic: ", topicx)
                        count = payload_str.strip('\x00\n')
                        print("count : ", count, type(count))
                        body['count'] = count
                        print("body : ", body)
                        publish(client, topicx, json.dumps(body))
                        if args.rssi:
                            rssi = int(payload[-1]) - 256
                            print(f"RSSI: {rssi} dBm")
                        print("RECEIVED\n")
                        payload = bytes()
                        print("payload:", payload)
    else:
        print("INVALID")
        return

if __name__ == "__main__":
    time.sleep(10)
    main()
```

### 起動

E220 moduleでLoRa通信するにはconfig modeでparameterをloadしてからoperation modeに移行してGW + brokerを起動する必要があります。

こちらもE220-900T22S(JP)_シンプルLoRaHAT_利用ガイド_Rev1.pdfに従い以下の順に操作します。

```sh
cd ./config_code
python mode3.py
python config_cui.py /dev/ttyS0 --apply
sleep 10

python mode0.py
sleep 5

cd
python gateway.py /dev/ttyS0 --rssi
```
以下のような画面となればAWS IoTと接続完了してLoRa NW向けには待ち受けの状態になっています。

```sh
~ $ python gateway.py  /dev/ttyS0 --rssi
serial port: /dev/ttyS0
receive waiting...
payload: b''
Connected
```


## 次回

[Private LoRa Gateway経由でnodeのデータをAWS IoTにpublish② E220でラズパイLoRaデバイス構築](https://qiita.com/Saito5656/items/3ee0535bee2e3b9a3812)

次回はedge nodeでの利用を想定したラズパイPrivate LoRa Deviceを構築します。