SeeedのGrove LoRa-E5モジュールを使用したDS18B20ベースのワイヤレス防水温度プローブの実装

概要

このプロジェクトでは、LoRaを介してワイヤレスで温度データを送信する防水温度プローブの実装について説明します。既存のChirpStackベースのプライベートLoRaWANネットワークとMachinechatのJEDI Pro IoTソフトウェアプラットフォームにセンサノードを追加することも取り上げます。このセンサは、DFR0198防水デジタル温度センサ、Seeeduino Xiaoコントローラ、およびGrove LoRa-E5無線ボードを使用しています。LoRaWANネットワークは、SeeedのIP67規格の産業用LoRaWANゲートウェイを使用して、LoRaセンサパケットをSeeedのReServer(Ubuntu linuxをインストール済み)上で動作するプライベートChirpStack LoRaWANネットワークサーバに転送しています。MachinechatのJEDI Pro IoTプラットフォームは、同じReServer上で動作します。

image

ハードウェア

  • Seeed reServer
    reServerはODYSSEY X86 v2ボードをベースに、Intel® Core™ 第11世代i3、Intel UHD Graphics 48EUsを搭載しています。
  • Seeed SenseCAP Outdoor Gateway - LoRaWAN US915MHz
    SenseCAP LoRaWAN Gatewayは、LoRaWAN®プロトコルに基づき、低電力、長距離の環境データ収集とモニタリングに適用されます。
  • Seeed Grove LoRa-E5 board
    STM32WLE5JC、SX126x LoRa RF Groveプラットフォーム評価用拡張ボード
  • Seeed Seeeduino Xiao board
    SAMD21G18 Seeeduino XIAOシリーズARM® Cortex®-M0 MCU 32ビット組込み評価ボード
  • DFRobot DFR0198 Waterproof Temperature Sensor
    1線式温度センサDS18B20の防水バージョンで、ステンレスチューブ入り、リード線が露出した長さ95cmのケーブル付きです。

ソフトウェア

  • JEDI Pro or JEDI Pro SSE
    IoTデータの収集、可視化、モニタリング、データストレージのための適応型ソフトウェアで、IoTソリューションに組み込むことができます。
       ・センサ、デバイス、マシンからのデータ収集
       ・直感的なリアルタイムおよび履歴データ、システムビューダッシュボードの構築
       ・データ状態を自動的に監視し対応するためのルール作成
       ・メールやSMSによるアラート通知の受信
    などの機能を備えています。
    JEDI Pro SSEは、JEDI ProにSeeedのSenseCAP LoRaWANセンサ用のData Collectorを追加したSeeed Studio Editionバージョンです。
  • ChirpStack
    ChirpStackは、オープンソースのLoRaWANネットワークサーバスタックで、LoRaWANネットワーク用のコンポーネントをオープンソースで提供します。モジュラーアーキテクチャにより、既存のインフラストラクチャに統合することが可能です。
  • Arduino
    Arduinoは、使いやすいハードウェアとソフトウェアをベースにしたオープンソースの電子機器プラットフォームです。

背景

この記事は、TechForumの関連記事「MachinechatとSeeed SenseCAPによるプライベートLoRaWANセンサネットワークの構築」を基にした後続プロジェクトで、Digi-Keyで入手できる既製のハードウェアとソフトウェアを使用してプライベートLoRaWAN IoTセンサネットワークを構築する詳細が記載されています。ソフトウェアとしては、MachinechatのJEDI ProアプリケーションソフトウェアJEDI ProとChirpStackのLoRaWANネットワークサーバアプリケーションが使用されました。ハードウェアは、Seeed reServer x86サーバとSenseCAP屋外用LoRaWANゲートウェイが使用されました。この記事では、Seeeduino Xiao、Grove LoRa-E5ボード、およびDFRobot DFR0198温度センサを使って、LoRaWANネットワーク経由で最低、最高、および平均温度を報告するように設定しています。

インプリメンテーション

このプロジェクトでは、外部USB 5V電源がSeeeduino Xiaoに電力を供給し、Xiaoの3V3出力がGrove LoRa-E5ボードとDFR0198センサに電力を供給しています。XiaoのUARTはGrove LoRa-E5のTX/RXピンに、Xiaoの入力ピン2はDS18B20ベースのデジタル温度センサの1-wireデータピンに接続されています。
Xiao(プロトボードにはんだ付けされたXiao Groveシールドに取り付けられている)とLoRa-E5ボードを屋外用筐体(Bud Industriesの品番PN1323-CMB)に取り付け、M8パネルマウントコネクタとケーブルアセンブリを使って+5V電源とDFR0198センサを接続しました。

Arduinoのアプリケーションコードは、Seeeduino Xiao上で実行され、LoRa-E5にATコマンドを送り、2番ピンを介してDS18B20へ1-Wireコマンドを送ります。アプリケーションはLoRaネットワークに接続し、DS18B20センサを読み取り、計算して最小/最大/平均温度データをCayenneLPPフォーマットにエンコードし、LoRaでデータを送信し、次のデータセットで同じことを繰り返すために遅延させてループバックします。以下の構成図は、回路がどのように配線され実装されているかを示したものです。
(Scheme-itプロジェクト:LoRaE5_Xiao_DS18B20tempSensor

Seeeduino Xiao、Grove LoRa-E5、およびDFR0198温度センサのセットアップ

1 - Seeeduino XiaoにArduinoをセットアップします。Getting started with Seeeduino Xiaoを参照してください。

2 - アプリケーションに必要なライブラリをインストールします。Arduinoのライブラリマネージャで、これらのライブラリを追加します。

3 - コードウォークスルー(ファイル名:Xia_LoRaE5CayenneLPP_DS18B20tempSensorRev2.ino)

初期セットアップ

// below LoRa rcode is based on Seeed LoRaE5 example code and modified to NOT use the display on the Seeeduino Xiao expansion board (just uses a Xiao connected to a LoRaE5 
// Grove expansion board. Seeed example code at https://wiki.seeedstudio.com/Grove_LoRa_E5_New_Version/
//
//note: all Seeed LoRaE5 Grove boards have example code App key of "2B7E151628AED2A6ABF7158809CF4F3C" so needs to be changed
// 

// Anemometer measuring code based on below example code:
//More Information at: https://www.aeq-web.com/
//Version 2.0 | 11-NOV-2020
//
//SBR modifications 2-NOV-2022 (file name: Xia_LoRaE5CayenneLPP_AnemRunAvg.ino)
//SensorPin is connected to RC pulldown circuit on anemometer switch
//Wind vane functionality removed from this version
//Anemometer used is industry #40R with 4 magnets per rev, windspeed formula is: (Hz * 1.38) + 1.17
//add in Running Average code that uses RunningAverage library
//update sampling times for average wind speed and max wind speed gust

//SBR modifications 28-DEC-2022 (file name: Xia_LoRaE5CayenneLPP_DS18B20tempSensorRev2.ino)
// remove anemometer related code and replace with DS18B20 temp sensor code

#include <Arduino.h>
#include <CayenneLPP.h>   //library for Cayenne Low Power Payload LPP encoding for use in LoRa payload

// this version uses RunningAverage code
#include "RunningAverage.h"
RunningAverage myRA(10);
int samples = 0;

// general parameters
const int RecordTime = 3; //Define Measuring Time (Seconds)
const int ledPin =  13;      // the number of the LED pin

float tempAvg;   //average temperature
float tempMax; //max temperature
float tempMin; //max temperature
  
// OneWire library and DallasTemperature library needed for DS18S20 code
#include <OneWire.h>
#include <DallasTemperature.h>

// Define to which pin of the Arduino the 1-Wire bus is connected:
#define ONE_WIRE_BUS 2

// Create a new instance of the oneWire class to communicate with any OneWire device:
OneWire oneWire(ONE_WIRE_BUS);

// Pass the oneWire reference to DallasTemperature library:
DallasTemperature sensors(&oneWire);


CayenneLPP lpp(51);   //setup Cayenne LPP (low power payload) buffer - per documentation 51 bytes is safe to send
 
static char recv_buf[512];
static bool is_exist = false;
static bool is_join = false;
static int led = 0;

int buf_size;  //Cayenne LPP buffer payload size
int Pointer;   //pointer used in Cayenne LPP buffer
int Offset = 12;    //offset to where Cayenne LPP payload data starts
int Loop1;       //loop counter in LoRa payload builder
int Loop2;       //loop counter in LoRa payload builder
int Loop3 = 0;       //loop counter in LoRa parameter send

 
static int at_send_check_response(char *p_ack, int timeout_ms, char *p_cmd, ...)
{
    int ch;
    int num = 0;
    int index = 0;
    int startMillis = 0;
    va_list args;
    memset(recv_buf, 0, sizeof(recv_buf));
    va_start(args, p_cmd);
    Serial1.printf(p_cmd, args);
    Serial.printf(p_cmd, args);
    va_end(args);
    delay(200);
    startMillis = millis();
 
    if (p_ack == NULL)
    {
        return 0;
    }
 
    do
    {
        while (Serial1.available() > 0)
        {
            ch = Serial1.read();
            recv_buf[index++] = ch;
            Serial.print((char)ch);
            delay(2);
        }
 
        if (strstr(recv_buf, p_ack) != NULL)
        {
            return 1;
        }
 
    } while (millis() - startMillis < timeout_ms);
    return 0;
}

 // LoRa message receive message buffer RSSI and SNR
static void recv_prase(char *p_msg)
{  
    if (p_msg == NULL)
    {
        return;
    }
    char *p_start = NULL;
    int data = 0;
    int rssi = 0;
    int snr = 0;
 
    p_start = strstr(p_msg, "RX");
    if (p_start && (1 == sscanf(p_start, "RX: \"%d\"\r\n", &data)))
    {
        Serial.println(data);
        led = !!data;
        if (led)
        {
            digitalWrite(LED_BUILTIN, LOW);
        }
        else
        {
            digitalWrite(LED_BUILTIN, HIGH);
        }
    }
 
    p_start = strstr(p_msg, "RSSI");
    if (p_start && (1 == sscanf(p_start, "RSSI %d,", &rssi)))
    {
         Serial.println(rssi);
    }
    p_start = strstr(p_msg, "SNR");
    if (p_start && (1 == sscanf(p_start, "SNR %d", &snr)))
    {
        Serial.println(snr);
    }
}

 
void setup(void)
{
    Serial.begin(9600);
    // Start up the library:
    sensors.begin();
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, HIGH);

    myRA.clear(); // explicitly start clean with RunningAverage by clearing buffer
 
    Serial1.begin(9600);                  //UART serial1 connection to LoRaE5
    Serial.print("E5 LORAWAN TEST\r\n");
 
    if (at_send_check_response("+AT: OK", 100, "AT\r\n"))
    {
        is_exist = true;
        //at_send_check_response("+ID: AppEui", 1000, "AT+ID\r\n");
        at_send_check_response("+MODE: LWOTAA", 1000, "AT+MODE=LWOTAA\r\n");
        //at_send_check_response("+DR: EU868", 1000, "AT+DR=EU868\r\n");  //original
        at_send_check_response("+DR: US915", 1000, "AT+DR=US915\r\n"); 
        //at_send_check_response("+CH: NUM", 1000, "AT+CH=NUM,0-2\r\n");  //original
        at_send_check_response("+CH: NUM", 1000, "AT+CH=NUM,8-15,65\r\n"); // configure channels to match chirpstack
        //at_send_check_response("+KEY: APPKEY", 1000, "AT+KEY=APPKEY,\"2B7E151628AED2A6ABF7158809CF4F3C\"\r\n");
        at_send_check_response("+CLASS: C", 1000, "AT+CLASS=A\r\n");
        at_send_check_response("+PORT: 8", 1000, "AT+PORT=8\r\n");
        at_send_check_response("+ID: AppEui", 1000, "AT+ID\r\n");
        at_send_check_response("+KEY: APPKEY", 1000, "AT+KEY=APPKEY,\"2B7E151628AED2A6ABF7158809CF4F3C\"\r\n");
        delay(200);
        is_join = true;
    }
    else
    {
        is_exist = false;
        Serial.print("No E5 module found.\r\n");
    }
 
}
  

メインループ - 温度測定、温度データのサンプリング、RunningAverageライブラリによる最小、最大、平均温度の決定、CayenneLPPフォーマットにエンコード、LoRaペイロードデータとしての送信

void loop(void)
//void loop()
{
     
    // measure DS18S20 temp and add to Running Average data table myRA
    sensors.requestTemperatures();
    // get the temperature in degrees Celsius for device index:
    float tempC = sensors.getTempCByIndex(0); // the index 0 refers to the first device
    Serial.println(tempC);
    delay(100);
    
    //measure();
    myRA.addValue(tempC);
    samples++;
    delay(100 * RecordTime);
    //debug code to print out sample# and min, average, max windspeed 
    Serial.print("samples = ");
    Serial.print(samples);
    Serial.print("\t");
    Serial.print("MinAvgMax  ");
    Serial.print(myRA.getMin(), 3);
    tempMin = myRA.getMin();
    Serial.print("\t");
    Serial.print(myRA.getAverage(), 3);
    tempAvg = myRA.getAverage();
    Serial.print("\t");
    Serial.println(myRA.getMax(), 3);
    tempMax = myRA.getMax();
    Serial.println(myRA.getMax(), 3);
    tempMax = myRA.getMax();
    
    // due to character byte limitatations in LoRa payload buffer, need to alternate between 
    // the three temp parameters when sending out parameter payload so use loop3
    
    Serial.print("Loop3 = ");  //debug code
    Serial.println(Loop3);    //debug code

    if ((Loop3 == 0) and (samples == 40))
    //when samples = 40, add WindSpeedAvg to LoRa payload (change to tempAvg)
    {
      lpp.reset();
      lpp.addAnalogOutput(7, tempAvg);  //channel 7, temp Avg
      Loop3 = 1;
      BuildPayload();
    }
    else if((Loop3 == 1) and (samples == 50))
    //when samples = 50, add WindSpeedGust to LoRa payload
    {
      lpp.reset();
      lpp.addAnalogOutput(8, tempMax);  //channel 8, temp Max
      //Loop3 = 0;
      BuildPayload();
    }
    else if((Loop3 == 1) and (samples == 60))
    //when samples = 60, add WindSpeedGust to LoRa payload
    {
      lpp.reset();
      lpp.addAnalogOutput(9, tempMin);  //channel 9, temp Min
      Loop3 = 0;
      BuildPayload();
    }    
    else
    {
      Serial.println("sample limits not reached");
    }

  // clear running average data after samples = 61   
  if (samples == 61)
  {
    samples = 0;
    myRA.clear();
    Serial.println("RunningAverage data cleared");
  }
    
}

LoRaペイロードの構築と送信機能

//LoRa payload builder and send function
void BuildPayload() {
    buf_size = lpp.getSize();
    Serial.print("Cayenne LPP buffer size = ");
    Serial.println(buf_size);

    uint8_t *payload = lpp.getBuffer();
    char cmd[128];
    
    // Lmsg is just test code that probably can be deleted?
    char Lmsg[4];
    for (unsigned char i = 0; i < lpp.getSize(); i++)
    {
      sprintf(Lmsg, "%02X", (char)payload[i]);
      Serial.print(Lmsg);
    }

    Serial.println(" payload"); 

//**************end of payload builder code**************************

    // start of LoRa communication code 
    if (is_exist)
    {
        int ret = 0;
        if (is_join)
        {
 
            ret = at_send_check_response("+JOIN: Network joined", 12000, "AT+JOIN\r\n");
            if (ret)
            {
                is_join = false;   //resets join check?
            }
            else
            {
                at_send_check_response("+ID: AppEui", 1000, "AT+ID\r\n");
                Serial.print("JOIN failed!\r\n\r\n");
                delay(5000);
            }
        }
        else
        {
            char cmd[128]; 
            //sprintf(cmd, "AT+CMSGHEX=\"%04X%04X\"\r\n", (int)temp, (int)humi); //original
            //****** add in CayenneLPP ecoding ******
            sprintf(cmd, "AT+CMSGHEX=\"%02X\"\r\n", (char)payload[0]);  //first part of confirmed message
            Serial.println("debug - print cmd at start of loop");
            Serial.println(cmd);
            // add data payload to LoRa message by looping thru Cayenne LPP data buffer
            char TestMsg[2];   
            for (Loop1 = 0; Loop1 < buf_size; Loop1++)
              {
                Pointer = (Loop1*2) + Offset;
                sprintf(TestMsg, "%02X", (char)payload[Loop1]);
                // write data buffer character to LoRa message
                for (Loop2 = 0; Loop2 < 2; Loop2++)
                  {
                     cmd[Loop2 + Pointer] = TestMsg[Loop2];
                  }
              }            
            // create end of message characters for LoRa message (need ",return,null characters)
            char EndMsg[20];
            sprintf(EndMsg, "test\"%02X\"\r\n", (char)payload[0]);
            // add ", return and null characters to end of LoRa message
            for (unsigned char i = 2; i < 7; i++)  
              {
                cmd[Pointer + i] = EndMsg[5 + i];
              }            
            Serial.println("debug - print cmd at end of loop");
            Serial.println(cmd);
            //******** end of CayenneLPP encodeding *********
            ret = at_send_check_response("Done", 12000, cmd);        //sends cmd command over LoRa - increase time from 5000 to 12000
            if (ret)
            {
                recv_prase(recv_buf);
            }
            else
            {
                Serial.print("Send failed!\r\n\r\n");
            }
            delay(5000); 
        }
    Serial.println(" in main loop checking LoRa then delay for 10 seconds");   
    delay(10000); // 10 seconds
    }
    else
    {
        delay(1000);
    }


}

Xia_LoRaE5CayenneLPP_DS18B20tempSensor.inoアプリケーションの最新のソースコードは、以下のリンク先のgithubにアップロードされています。

Grove LoRa-E5ボードのデバイスEUIの決定

ArduinoでXia_LoRaE5CayenneLPP_DS18B20tempSensorRev2.inoのコードをコンパイルしてSeeeduino Xiaoにアップロードし、Serial Monitorを有効にします。Serial Monitor出力を見て、Grove LoRa-E5のデバイスEUIを決定します。

image

LoRa-E5ベースのセンサノードをChirpStack LoRaWANネットワークサーバに追加

(注:このプロジェクトと以下のステップは、ChirpStackベースのプライベートLoRaWANネットワークがアクティブで、LoRa-E5センサノードの範囲内にあることを前提としています。そうでない場合は、TechForumの投稿「MachinechatとSeeed SenseCAPによるプライベートLoRaWANセンサネットワークの構築」をご参照ください。)

1 - ChirpStackで、Device-profileを選択し、Createを選択します。Device-profileに「Seeed LoRaE5」を入力し、LoRaWAN MACバージョンは「1.0.2」、LoRaWAN Regional Parametersバージョンは「A」、ADR algorithmは「Default ADR algorithm」を選択し、Uplink intervalには「3600」を入力します。JOIN(OTAA/ABPタブで、「Device supports OTAA」ボックスにチェックを入れます。CODEC タブで、CODECドロップダウンリストから「Cayenne LPP」を選択します。(注:これらのパラメータを持つデバイスプロファイルを既に作成している場合は、新しいプロファイルを作成する必要はありません。選択するだけで済みます。)

2 - ChirpStackで、Applicationsを選択し、「FarmTest」を選択し、Creatを選択します。Device nameに「LoRaE5tempDS18B20」(またはお好きな名前)を入力し、Device descriptionに「description」を入力し、Grove LoRa-E5ボードのデバイスEUI(上記のステップ「Grove LoRa-E5ボードのデバイスEUIの決定」から決定)を入力し、Device-profileに「Seeed LoRaE5」を入力し、そしてCREATE DEVICEを選択します。(注1: 最初のテストとデモでは、Disable frame-counter validation(フレームカウンタの検証を無効にする)ボックスにチェックを入れることをお勧めします。)

3 - DeviceのApplication keyを追加します。Application keyに「2B7E151628AED2A6ABF7158809CF4F3C」(注:これはLoRa-E5のデフォルトキーです。変更するにはLoRa-E5 AT Command SpecificationのKEYセクションを参照)を入力し、SET DEVICE-KEYSを選択します。

JEDI Pro Generic LoRaWAN Custom Data CollectorとChirpStackとのHTTP Integrationのセットアップおよびテスト

ChirpStackが変更され、LoRaWANメタデータおよびセンサデータを指定されたIPアドレスに転送するためのHTTP Integrationが追加されました。MachinechatのGeneric LoRaWAN Custom Data Collector Plug-inは、指定されたIPアドレスをリッスンし、JEDI Proプラットフォームでレビュー(デバッグ有効時)および使用するためのLoRaWANデータを解析するために使用されます。
(注:Custom Data Collectorは、lorawan-linux.binとconfig.ymlの2つのファイルで構成されており、Machinechat から以下のサイトで入手可能です。https://support.machinechat.io/hc/en-us/articles/6046199010327-Generic-LoRaWAN-Custom-Data-Collector-Beta-for-JEDI-PRO-Linux

1 - ChirpStackでHTTP Integrationを有効にします。
「Applications」で「FarmTest」を選択し、それから「Integration」タブを選択します。ChirpStack Integrations画面で「Add」を選択します。
(注:以前のデバイスに「FarmTest」HTTP Integrationが既に実装されている場合は、ステップ1~4をスキップして、直接ステップ5に進み、config.ymlファイルを編集することができます)

2 - HTTP Integrationを構成します。
Payload marshalerで「JSON」を選択し、Endpoint URLにIPアドレス(config.ymlファイルと同じIPを使用)を追加し、ADD INTEGRATIONを選択します。

3 - lorawan-linux.binconfig.ymlファイルをUbuntu Linux Mini-PCのJEDI Proがインストールされている~/jedi/pluginsディレクトリにコピーします。config.ymlファイルを修正して、デバッグを有効にし、IPリスニングアドレスを指定します。
(注:もし、以前に別のセンサ用にlorawan-linux.binconfig.ymlァイルをインストールしたことがあれば、ステップ5のようにconfig.ymlを編集して温度パラメータ用の情報を追加するだけです)

4 - Ubuntu Linux Mini-PCの端末で「./lorawan-linux.bin ./config.yml」を使ってコマンドラインからCustom Plug-Inを実行し、出力データをモニタします。データは以下のようになります(注:lorawan-linux.binファイルを実行可能にしておくことを忘れないでください)。

5 - config.ymllファイルを編集し、LoRaWANデータをJEDI Proのデータパラメータにマッピングし、デバッグを無効にします。このプロジェクトの例では、LoRaWANのcSproperty: “analogOutput.7”mcProperty: “tempAvg”にマッピングされ、LoRaWANのcSproperty: “analogOutput.8”mcProperty: “tempMax”にマッピングされ、LoRaWANのcSproperty: “analogOutput.9”mcProperty: “tempMin”にマッピングされるようにpropertyNamesを編集します。「setDebug:」をfalseにしてデバッグを無効にし、ファイルを保存します。
(注:変更を反映させるためには、ChirpStackが動作しているサーバを再起動する必要があります)

JEDI Pro Custom Data CollectorとData Dashboardのセットアップ

JEDI Proで「Settings」タブを選択し、「Data Collectors」を選択し、「Add Collector」を選択します。(注:JEDIアプリケーションにLoRaWAN custom data collectorを追加したことがある場合、これらのステップは必要ありませんし、以下の「Data Dashboard」ステップでチャートを追加することができます。)

Collectorのセットアップは以下のように行います。Data Collectorの名前を「LoRaWAN」(またはお好きな名前)とし、Collector Typeとして「Custom Plug-In」を選択し、Plug-In Executable fileとして「lorawan-linux.bin」を選択して、Plug-in Optionsのconfig.yml fileの場所(例えば、「/home/scutr/jedi/plugins/config.yml」)を入力し、「Run As Background Process and Monitor」にチェックを入れ、「VALIDATE PLUG-IN」を選択して機能を確認します。

JEDI Proで「Data Dashboards」を選択し、「+」を選択して新しいチャートを追加します。DS18B20の平均温度、最高温度、最低温度のデータチャートを設定し、「Add」を選択するとData Dashboardに追加されます(以下の例を参照してください)。

まとめ

Arduino、Seeeduino Xiao、LoRa-E5無線ボード、およびDFRobotのDS18B20ベースのデジタル温度プローブを組み合わせることで、LoRaを介して無線でパラメトリック温度データを提供する、柔軟で拡張性の高い温度モニタリングプラットフォームとなります。ChirpStackのHTTP IntegrationとMachinechatのGeneric LoRaWAN Custom Data Collectorを設定して、温度センサのデータをJEDI Proに取り込み、IoTデータの収集、可視化、モニタリング、そしてデータストレージを行います。このサンプルコードは、必要に応じて防水型DS18B20温度プローブや他のセンサを追加するために簡単に修正することができます。

参考資料




オリジナル・ソース(English)