概要
このプロジェクトでは、Seeed LoRa-E5ベースのArduino風速/風向計を開発し、Digi-Key本社ビルの屋上にある気象プラットフォームに取り付け可能な屋外筐体に収納するまでの過程を説明します。また、既存のChirpStackベースのプライベートLoRaWANネットワークとMachinechatのJEDI Pro IoTソフトウェアプラットフォームにセンサノードを追加することも取り上げます。LoRaセンサは、Seeeduino Xiao、Grove LoRa-E5無線ボード、SparkfunのWeather Meterキットの風速/風向計のハードウェアを使用しています。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ビット組込み評価ボード -
Sparkfun Weather Meter Kit
このキットでは、気象計測の中核となる風速、風向、雨量の3つを提供します(注:この記事では風速計と風向計のセンサを使用しています。雨量計のセンサは今後の記事で紹介する予定です)。
ソフトウェア
-
JEDI Proまたは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アプリケーションソフトウェア、ChirpStackのLoRaWANネットワークサーバアプリケーションが使用されました。ハードウェアは、Seeed reServer x86サーバとSenseCAP屋外用LoRaWANゲートウェイが使用されました。この記事では、Seeeduino Xiao、Grove LoRa-E5ボード、Sparkfunの気象計キットを使って、LoRaWANネットワーク経由で風速と風向を報告するように設定しています。
Sparkfun気象計キット 風速/風向計
風速は、磁石を回転させることで、接点を1回転に1回閉じるカップ式風速計で測定します。1秒間に1回接点が閉じれば、風速は1.492MPH(0.66m/s)に相当します。風向計ケーブルのRJ-11コネクタの2本の内側の導線(ピン2、3)を介して接点の閉状態を監視することができます。
風向は、風向計にある8つのスイッチとそれぞれの抵抗で決定されます。風向計に内蔵された磁石により、風向きが変わると各スイッチが閉じられます。 外部抵抗で分圧を行い、出力電圧を監視して風向計の位置を知ることができます。RJ-11コネクタの2本の外側の導線(ピン1、4)は風向計に使用されます(注:詳細な情報は、SparkfunのWeather Meter Hookup Guideをご覧ください)。
Digi-Key気象プラットフォーム
Digi-Key気象プラットフォームについての詳細は次のリンクをご覧ください。Digi-Key Weather Platform
インプリメンテーション
このプロジェクトでは、外部USB 5V電源がSeeeduino Xiaoに電力を供給し、Xiaoの3.3V出力がGrove LoRa-E5ボードと気象センサハードウェアに電力を供給しています。XiaoのUARTはGrove LoRa-E5のTX/RXピンに、Xiaoの入力ピン1と2は風向計と風速計センサに接続されています。
XiaoとLoRa-E5ボードを屋外用筐体(Hammod Manufacturing 品番RL6225)に取り付け、M8パネルマウントコネクタとケーブルアセンブリを使って+5V電源と風速/風向計センサを接続しました。
Arduinoのアプリケーションコードは、Seeeduino Xiao上で実行され、LoRa-E5にATコマンドを送り、入力ピン1、2の監視を行っています。 アプリケーションはLoRaネットワークに接続して、入力ピンを監視して風速/方向データを計算し、データをCayenneLPPフォーマットでエンコードしてLoRaで送信し、遅延させて次の読み取りをループバックします。下図は、回路がどのように配線され、実装されているかを示したものです(Scheme-itプロジェクト:LoRaE5_Xiao_WindSensorM8cables を参照)。
モジュールと風速/風向センサコネクタJ1間の電気的接続を以下に示します。
Seeeduino Xiaoピン | Grove LoRa-E5コネクタ | 風速/風向センサコネクタJ1 |
---|---|---|
GND | GND | 1 |
3V3 | VCC | 2 |
1 | 4 (風向計) | |
2 | 3 (風速計) | |
6 | RX | |
7 | TX |
Seeeduino Xiao、Grove LoRa-E5、および風速/風向計のセットアップ
1 - Seeeduino XiaoにArduinoをセットアップします。 Getting started with Seeeduino Xiaoを参照。
2 - アプリケーションに必要なライブラリをインストールします。Arduinoのライブラリマネージャでライブラリを追加します。
3 - コードウォークスルー(ファイル名: Xiao_LoraE5CayenneLPP_WindRev1.ino)
初期設定
// below code 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
// this version changes out the DHT11 sensor for Sparkfun weather meter kit for wind speed and direction
// Anemometer measuring code based on below:
//More Information at: https://www.aeq-web.com/
//Version 2.0 | 11-NOV-2020
//
//SBR modifications
//SensorPin is connected to RC pulldown circuit on anemometer switch
//Wind vane pin is connected to voltage divider circuit for vane resistors
#include <Arduino.h>
#include <CayenneLPP.h>
// anemometer and direction parameters
const int RecordTime = 3; //Define Measuring Time (Seconds)
const int SensorPin = 2; // the number of the sensorpin
const int VanePin = 1; // pin# connected to wind vane
const int ledPin = 13; // the number of the LED pin
int InterruptCounter;
float WindSpeed;
float DirWind; //wind direction voltage
float angleWind; //wind vane angle
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;
}
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);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
// anemeometer and wind vane setup
pinMode(SensorPin, INPUT); //use input to count interrupts of anemometer
pinMode(VanePin, INPUT); //use input to read analog voltage of wind vane output
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");
delay(200);
is_join = true;
}
else
{
is_exist = false;
Serial.print("No E5 module found.\r\n");
}
}
メインループ - 風速と風向きを決定し、CayenneLPPフォーマットでエンコードしてLoRaペイロードデータとして送信
void loop(void)
//void loop()
{
// measure windspeed
measure();
// measure wind vane voltage
DirWind = analogRead(VanePin)*3.33;
// calculate wind direction in degrees
vaneAngle();
Serial.print("Windspeed: ");
Serial.print(WindSpeed);
Serial.print(" mph ");
Serial.print("Wind Direction: ");
Serial.print(angleWind);
Serial.println(" degrees");
//***************** start of Cayenne LPP code **************************
// due to character byte limitatations in LoRa payload buffer, need to alternate between
// the two wind parameters when sending out parameter payload
Serial.print("Loop3 = "); //debug code
Serial.println(Loop3); //debug code
if (Loop3 == 0)
{
lpp.reset();
lpp.addAnalogOutput(1, WindSpeed); //channel 1, windspeed value
Loop3 = 1;
}
else
{
lpp.reset();
lpp.addAnalogOutput(2, angleWind); //channel 2, wind direction value
Loop3 = 0;
}
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 wait 5 minutes");
delay(300000); //main delay 300 seconds
}
else
{
delay(1000);
}
}
センサのサブルーチン
//count anemometer pulses and calculate windspeed
void measure() {
InterruptCounter = 0;
attachInterrupt(digitalPinToInterrupt(SensorPin), countup, RISING);
delay(1000 * RecordTime);
detachInterrupt(digitalPinToInterrupt(SensorPin));
WindSpeed = (float)InterruptCounter / (float)RecordTime * 2.4;
}
void countup() {
InterruptCounter++;
}
//determine wind direction angle based on measured wind vane voltage
void vaneAngle() {
angleWind = 99.9;
if (DirWind < 270)
{
angleWind = 112.5;
}
else if ((DirWind >= 270) and (DirWind <= 310))
{
angleWind = 67.5 ;
}
else if ((DirWind > 310) and (DirWind <= 400))
{
angleWind = 90.0 ;
}
else if ((DirWind >= 400) and (DirWind < 600))
{
angleWind = 157.5 ;
}
else if ((DirWind >= 600) and (DirWind < 750))
{
angleWind = 135.0 ;
}
else if ((DirWind >= 750) and (DirWind < 850))
{
angleWind = 202.5 ;
}
else if ((DirWind >= 850) and (DirWind < 1150))
{
angleWind = 180.0 ;
}
else if ((DirWind >= 1150) and (DirWind < 1450))
{
angleWind = 22.5 ;
}
else if ((DirWind >= 1450) and (DirWind < 1900))
{
angleWind = 45.0 ;
}
else if ((DirWind >= 1900) and (DirWind < 2050))
{
angleWind = 247.5 ;
}
else if ((DirWind >= 2050) and (DirWind < 2250))
{
angleWind = 225.0 ;
}
else if ((DirWind >= 2250) and (DirWind < 2500))
{
angleWind = 337.5;
}
else if ((DirWind >= 2500) and (DirWind < 2700))
{
angleWind = 0.0;
}
else if ((DirWind >= 2700) and (DirWind < 2850))
{
angleWind = 292.5;
}
else if ((DirWind >= 2850) and (DirWind < 3000))
{
angleWind = 315.0;
}
else if (DirWind >= 3000)
{
angleWind = 270.0;
}
}
Xiao_LoraE5CayenneLPP_WindRev1.ino アプリケーションの最新のソースコードは、以下のリンクの github にアップロードされています。
Grove LoRa-E5 ボードのデバイスEUI の決定
ArduinoでXiao_LoraE5CayenneLPP_WindRev1.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 nameに「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」を選択し、Createを選択します。Device nameに 「LoRaE5wind」を入力し、Device descriptionに「description」を入力し、Device EUIにGrove LoRa-E5ボードのデバイスEUI(上記ステップ 「Determine Grove LoRa-E5 board device EUI」から決定)を入力し、 Device-profileに「STM32WL Sensors」を入力し、CREATE DEVICEを選択します。(注:最初のテストとデモでは、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アドレスをリッスンし、LoRaWANデータを解析し、レビュー(デバッグ有効時)およびJEDI Proプラットフォームで使用するために使用されます。
(注: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を有効にする。
ChirpStack Integrations画面で「Add」を選択します。
2 - HTTP Integrationの構成
Payload marshalerで「JSON」を選択し、Endpoint URLにIPアドレス(config.ymlファイルと同じIPを使用)を追加し、ADD INTEGRATIONを選択します。
3 - Ubuntu Linux Mini-PCのJEDI Proがインストールされている~/jedi/pluginsディレクトリにlorawan-linux.binとconfig.ymlファイルをコピーします。config.ymlファイルを修正して、デバッグを有効にし、IPリスニングアドレスを指定します。
(注: もし、以前に別のセンサ用にlorawan-linux.binとconfig.ymlをインストールしたことがあれば、手順5のようにconfig.ymlを編集して風速と風向のパラメータ情報を追加するだけです。)
4 - Ubuntu Linux Mini-PCの端末で、「./lorawan-linux.bin ./config.yml」 を使ってコマンドラインからカスタムプラグインを実行し、出力データをモニタしてください。データは以下のようになるはずです(注:lorawan-linux.binファイルを実行可能にしておくことを忘れないでください)。
5 - config.ymlファイルを編集し、LoRaWANデータをJEDI Proのデータパラメータにマッピングし、デバッグを無効にします。このプロジェクトの例では、propertyNamesを、LoRaWANのcSproperty: “anslogOutput.1” が mcProperty: “WindSpeed” にマッピングされるように、LoRaWANのcSproperty: “analogOutput.2” が mcProperty: “WindAngle” にマッピングされるように編集して下さい。「setDebug:」をfalseにしてデバッグを無効にし、ファイルを保存します。
JEDI Pro Custom Data CollectorとData Dashboardのセットアップ
JEDI Proで、「Settings」タブを選択し、「Data Collectors」を選択し、「Add Collector」を選択します。(注:JEDIアプリケーションにLoRaWAN Custom Data Collectorを追加したことがある場合、このステップは必要ありません)
Collectorの設定は以下のように行います。Data Collectorの名前を「LoRaWAN」(またはお好きな名前)とし、Collector Typeとして「Custom Plug-In」を選択し、Plug-In Executable fileとして「lorawan-linux.bin」を選択して、config.ymlファイルの場所を入力します。(例: プラグインオプションに「/home/scottr/jedi/plugins/config.yml」を入力し、「Run As Background Process and Monitor」にチェックを入れ、 「VALIDATE PLUG-IN」を選択して機能を確認します。)
JEDI Proで「Data Dashboards」を選択し、「+」を選択して新しいチャートを追加します
風速(WindSpeed)と風向(WindAngle)のデータグラフを設定し、「追加」を選択すると、Data Dashboardに表示されます(以下の例を参照してください)。
まとめ
Arduino、Seeeduino Xiao、LoRa-E5無線ボード、およびSparkfunのWeather Meterキットを組み合わせることで、LoRaで風速や風向のデータを提供する柔軟な風速/風向センサプラットフォームとなります。ChirpStackのHTTP IntegrationとMachinechatのGeneric LoRaWAN Custom Data Collectorを設定して、風センサのデータをJEDI Proに取り込み、IoTデータの収集、可視化、モニタリング、およびデータ保存を行います。このサンプルコードは、必要に応じて他のセンサ用に簡単に変更することができます。
参考資料
- 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
- Sparkfun - Weather Meter Hookup Guide