概要
このプロジェクトでは、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上で動作します。
ハードウェア
-
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を決定します。
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.binとconfig.ymlファイルをUbuntu Linux Mini-PCのJEDI Proがインストールされている~/jedi/pluginsディレクトリにコピーします。config.ymlファイルを修正して、デバッグを有効にし、IPリスニングアドレスを指定します。
(注:もし、以前に別のセンサ用にlorawan-linux.binとconfig.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温度プローブや他のセンサを追加するために簡単に修正することができます。
参考資料
- Seeed - Getting Started with reServer
- Seeed - SenseCAP Outdoor Gateway - LoRaWAN US915MHz
- Seeed - Seeeduino Xiao Wiki
- Seeed - Grove - LoRa-E5 Board
- Seeed - LoRa-E5 AT Command Specification
- Getting Started with machinechat’s JEDI One IoT Platform
- Machinechat - Building a private, edge-based LoRaWAN IoT sensor network