やったこと
- The Things Network(以下TTN)をLoRaWANサーバとして利用する
- LoRaWANゲートウェイとして DRAGINO LoRa/GPS HAT v1.4-JP + ラズパイ3を利用する
- LoRaWANノードとしてEASEL社の温湿度センサ ES920LRTH2 を繋いで、動作確認を行う
構成
シンプルにGateway1台×温湿度センサユニットの構成で試してみます。
LoRaWAN Gateway
LoRa Node (ES920LRTH2: 温湿度センサ)
ざっくり進め方
- 1. LoRaWAN ゲートウェイのセットアップ
- 2. LoRaWAN サーバ(TTN)のセットアップ(1)
- 3. LoRa ノード(温湿度センサ)のセットアップ
- 4. LoRaWAN サーバ(TTN)のセットアップ(2)
1. LoRaWAN ゲートウェイのセットアップ
ネットを探すといろいろ情報が出てきますが、まとまっているページがなかなか見つからなかったので、まずは一次資料である開発元のwikiを参考にしましたが、情報がかなり古く参考となったのはラズパイのSPIを有効にする部分のみでした。
Configuration
- Connect the Raspberry Pi to the Internet;
- Use 'raspi-config ' to ensure that SPI can be used on RPi ;
- Use 'git clone git://git.drogon.net/wiringPi' to install the GPIO access library written in C for the BCM2835 used in the Raspberry Pi;
- Get the single channel LoRa Gateway source code from here ;
- Edit the 'main.cpp' to change configuration (look for: "Configure these values!").
引用元)
http://wiki.dragino.com/index.php?title=Use_Lora/GPS_HAT_%2B_RaspberryPi_to_set_up_a_Lora_Node
- wiringPiは現時点のRaspberryPI OSであればすでにインストール済み
- LoRa Gatewayサーバのサンプルソースが古かったので、他サイトを参照にDual Channel LoRaWAN Gateway を利用することにしました。
1-1. ラズパイ3のSPIを有効にする
raspi-config
を利用して、SPIを有効にするだけ。
sudo raspi-config
3. Interface Options
-> P4 SPI
から有効にします。
1-2. Gatewayサーバ用のプログラムDual Channel LoRaWAN Gatewayのセットアップ
現時点(2021/01/24)で動かすには日本専用の設定変更に加えていろいろと変更しなければならない点があったのでまるっと修正したソースをgithubに上げました。
参考)ソースの修正箇所詳細について
https://github.com/bokse001/dual_chan_pkt_fwd をcloneして日本仕様に書き換えます1
まずは global_conf.json
を書き換えます。
{
"SX127x_conf":
{
"freq": 923400000, <--- 日本の周波数帯(デフォルトチャンネル)
"freq_2": 923200000, <---
"spread_factor": 10, <--- SF値(拡散率)ここは調整してもよい
"pin_nss": 6, <--- DRAGINO LoRa/GPS HATのピンに合わせる
"pin_dio0": 7, <---
"pin_nss_2": 6, <---
"pin_dio0_2": 7, <---
"pin_rst": 3, <---
"pin_led1":4,
"pin_NetworkLED": 22,
"pin_InternetLED": 23,
"pin_ActivityLED_0": 21,
"pin_ActivityLED_1": 29
},
"gateway_conf":
{
"ref_latitude": 0.0, <-- ここはGatewayの設置場所にあわせましょう
"ref_longitude": 0.0, <--
"ref_altitude": 10, <--
"name": "your name", <-- このあたりも適切に
"email": "a@b.c", <--
"desc": "Single channel pkt forwarder",
"interface": "eth0", <-- ラズパイでインターネット接続に利用する
"servers":
[
{
"address": "router.jp.thethings.network", <-- 日本向けに変更
"port": 1700,
"enabled": true
},
{
"address": "router.as1.thethings.network", <-- TODO: 周波数帯からAS1を選択してみたけど、これでよいのか?
"port": 1700,
"enabled": false
}
]
}
}
serversは ここ を参照に書き換えました。
次に dual_chan_pkt_fwd.cpp
を書き換えます。
まずは定数定義。#defineの定義部分(230行目付近)に追加しました。
// Tx Power Register add
// Power Setting for Japan ARIB STD-T108
// 20mW=13dBm
// SX1276 RegPaConfig(0x09), Val=0x3f
// bit 7 PaSelect = 0 select RFO
// bit 6-4 MaxPower = 3 Pmax=10.8+0.6*3=12.6 < 13
// bit 3-0 OutputPower = 0x0f Pout=Pmax-(15-0x0f)=12.6
// SX1272 RegPaConfig(0x09), Val=0x0e
// bit 7 PaSelect = 0 select RFO
// bit 6-4 unused = 0
// bit 3-0 OutputPower = 0xe Pout=-1 + OutputPower = -1 + 0x0e = 13dBm
#define PWR_JPN_1276 0x3f
#define PWR_JPN_1272 0x0e
実際の書き込み部分は433行目付近に入れました。
// Set Tx Power for Japan
if (sx1272) {
WriteRegister(REG_PA_CFG,PWR_JPN_1272, CE);
} else {
// sx1276
WriteRegister(REG_PA_CFG,PWR_JPN_1276, CE);
}
ここでmakeしたところ下記のwarningが発生しました。
include/rapidjson/document.h:1652:24: warning: ‘void* memcpy(void*, const void*, size_t)’ writing to an object of type ‘rapidjson::GenericValue<rapidjson::UTF8<> >::Member’ {aka ‘struct rapidjson::GenericMember<rapidjson::UTF8<>, rapidjson::MemoryPoolAllocator<> >’} with no trivial copy-assignment; use copy-assignment instead [-Wclass-memaccess]
std::memcpy(data_.o.members, members, count * sizeof(Member));
そこで、エラーが出ていた箇所を書き換えました。
// Initialize this value as array with initial data, without calling destructor.
void SetArrayRaw(GenericValue* values, SizeType count, Allocator& allocator) {
flags_ = kArrayFlag;
if (count) {
data_.a.elements = (GenericValue*)allocator.Malloc(count * sizeof(GenericValue));
std::memcpy(data_.a.elements, values, count * sizeof(GenericValue));
}
else
data_.a.elements = NULL;
data_.a.size = data_.a.capacity = count;
}
void SetArrayRaw(GenericValue* values, SizeType count, Allocator& allocator) {
flags_ = kArrayFlag;
if (count) {
// data_.a.elements = (GenericValue*)allocator.Malloc(count * sizeof(GenericValue));
// std::memcpy(data_.a.elements, values, count * sizeof(GenericValue));
auto arr = static_cast<GenericValue*>(allocator.Malloc(count * sizeof(GenericValue)));
for (SizeType idx = 0; idx < count; ++idx)
new (arr + idx) GenericValue;
data_.a.elements = arr;
std::copy_n(values, count, arr);
}
else
data_.a.elements = NULL;
data_.a.size = data_.a.capacity = count;
}
SetObjectRaw
も同じように警告がでていたので書き換えました。
今度はmakeが成功したので実行してみたところ、以下のエラーでサーバの起動に失敗しました。。。
pi@gateway:~/project/dual_chan_pkt_fwd $ ./dual_chan_pkt_fwd
dual_chan_pkt_fwd: include/rapidjson/document.h:1398: unsigned int rapidjson::GenericValue<Encoding, Allocator>::GetUint() const [with Encoding = rapidjson::UTF8<>; Allocator = rapidjson::MemoryPoolAllocator<>]: Assertion `flags_ & kUintFlag' failed.
中止
修正方法がちょっとわからなかったので、さくっとASSERTをコメントアウトしてみました。。。誰か正しい修正方法を教えて下さい😢
int GetInt() const { RAPIDJSON_ASSERT(flags_ & kIntFlag); return data_.n.i.i; }
unsigned GetUint() const { RAPIDJSON_ASSERT(flags_ & kUintFlag); return data_.n.u.u; }
int64_t GetInt64() const { RAPIDJSON_ASSERT(flags_ & kInt64Flag); return data_.n.i64; }
uint64_t GetUint64() const { RAPIDJSON_ASSERT(flags_ & kUint64Flag); return data_.n.u64; }
int GetInt() const { RAPIDJSON_ASSERT(flags_ & kIntFlag); return data_.n.i.i; }
//unsigned GetUint() const { RAPIDJSON_ASSERT(flags_ & kUintFlag); return data_.n.u.u; }
unsigned GetUint() const { return data_.n.u.u; }
int64_t GetInt64() const { RAPIDJSON_ASSERT(flags_ & kInt64Flag); return data_.n.i64; }
uint64_t GetUint64() const { RAPIDJSON_ASSERT(flags_ & kUint64Flag); return data_.n.u64; }
一応これで動いているのでとりあえず良しとします😇
1-2-1. 修正箇所
上記ソースコードを元にした、個別の修正箇所を記載します。
必須
global_conf.json
の以下の箇所を修正してください
...省略...
"gateway_conf":
{
"ref_latitude": 0.0, <-- ここはGatewayの設置場所にあわせましょう
"ref_longitude": 0.0, <--
"ref_altitude": 10, <--
"name": "your name", <-- このあたりも適切に
"email": "a@b.c", <--
"desc": "Single channel pkt forwarder",
"interface": "eth0", <-- ラズパイでインターネット接続に利用する
任意
ソースを展開したパスに併せて dual_chan_pkt_fwd.service
を修正してください。修正しない場合、 make install
で失敗する場合があります。
WorkingDirectory=/home/pi/dual_chan_pkt_fwd/
ExecStart=/home/pi/dual_chan_pkt_fwd/dual_chan_pkt_fwd
1-2-2. ソースコードのコンパイル
普通に make & make install する流れでOKです。
$ cd dual_chan_pkt_fwd
$ make
$ make install
sudo cp -f ./dual_chan_pkt_fwd.service /lib/systemd/system/
sudo systemctl enable dual_chan_pkt_fwd.service
sudo systemctl daemon-reload
sudo systemctl start dual_chan_pkt_fwd
sudo systemctl status dual_chan_pkt_fwd -l
● dual_chan_pkt_fwd.service - Lora Packet Forwarder
Loaded: loaded (/lib/systemd/system/dual_chan_pkt_fwd.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2021-01-24 14:52:03 JST; 5s ago
Main PID: 19602 (dual_chan_pkt_f)
Tasks: 1 (limit: 2063)
CGroup: /system.slice/dual_chan_pkt_fwd.service
└─19602 /home/pi/project/dual_chan_pkt_fwd/dual_chan_pkt_fwd
1月 24 14:52:03 gateway systemd[1]: Started Lora Packet Forwarder.
上記のようにログが出れば起動OKです!
後々の処理のため、一旦サーバを停止しておきましょう。
$ sudo systemctl stop dual_chan_pkt_fwd
ここで、TTNへのゲートウェイへの登録に必要なゲートウェイのEUIを確認します。直接動かすとサーバ起動時に表示されますので以下で確認してください
$ ./dual_chan_pkt_fwd
server: .address = router.jp.thethings.network; .port = 1700; .enable = 1
server: .address = router.as1.thethings.network; .port = 1700; .enable = 0
Gateway Configuration
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Single channel pkt forwarder
Latitude=xxxxxxx
Longitude=xxxxxxxx
Altitude=0
Interface: eth0
Trying to detect module CE0 with NSS=6 DIO0=7 Reset=3 Led1=unused
SX1276 detected on CE0, starting.
Trying to detect module CE1 with NSS=6 DIO0=7 Reset=3 Led1=unused
SX1276 detected on CE1, starting.
Gateway ID: XX:XX:XX:XX:XX:XX:XX:XX <---- ここがゲートウェイEUIなのでメモっておいてください!
Listening at SF10 on 923.400000 Mhz.
Listening at SF10 on 923.200000 Mhz.
-----------------------------------
stat update: 2021-01-24 09:57:17 GMT no packet received yet
このあとGatewayを登録しますので、起動したままにしておきましょう。
2. LoRaWAN サーバ(TTN)のセットアップ(1)
2-1. Gatewayのセットアップと動作確認
ゲートウェイを登録します。
ゲートウェイEUIには先にメモっておいたものを入力してください。
登録後下記のようにステータスが接続済みになれば登録完了です。
2-2. アプリケーションのセットアップとDeviceの登録
アプリケーションを作成します。
アプリケーション作成後、デバイスを登録します。
※なお、デバイスEUIは自動生成しますがい、あとで変更します。
登録後、デバイスの設定をES920LRTH2向けに変更しておきます。
登録完了後下記画面となりますので、
- デバイスアドレス
- ネットワークセッションキー
- Appセッションキー
をメモしておいてください。このあとES920LRTH2で利用します。
ちなみに独自に生成できない/しないほうがよい項目は下記の2個です。混乱の元となるのでメモしておきます。
- DevEUI(TTNコンソール:デバイスEUI)
- ES920LRTH2の出荷時にデフォルトセットされている。変更もできるが変えないほうがよい。ES920LRTH2の値を見てTTNコンソール側を書き換える。
- DevAddr(TTNコンソール:デバイスアドレス)
- TTNコンソールでアプリケーションからデバイス登録する際にTTN側で発行する。ES920LRTH2側で設定する。
3. LoRa ノード(ES920LRTH2: 温湿度センサ)のセットアップ
ES920LRTH2のドキュメントを元にシリアル接続し、下記値をセットしてください。
- Activate
- Activation by Personalization をセット
- DevAddr
- 上記でメモった デバイスアドレス をセット
- NwkSKey
- 上記でメモった ネットワークセッションキー をセット
- AppSKey
- 上記でメモった Appセッションキー をセット
- Acknowledge
- OFF をセット(ちょっと触った限りでは、Acknowledgeが受け取れなかった為)
その他はデフォルトで構いません。また、以降の処理で使いますので、 DevEUI
を確認しておいてください。
LoRaWAN > y
configuration setting is below
-------------------------------------
Class : Class A
ADR : ON
Activate : Activation by Personalization
DevEUI : xxxxxxxxxxxxxxxx <--- ここをメモっておく
AppEUI : 0000000000000000
AppKey : 00000000000000000000000000000000
DevAddr : xxxxxxxx
NwkSKey : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AppSKey : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Acknowledge : OFF
Default Data rate : DR2
Output Power : 13dBm
Send Interval : 60
4. LoRaWAN サーバ(TTN)のセットアップ(2)
4-1. DeviceのデバイスEUIを変更
上記処理で取得した DevEUI
でデバイスEUIを更新します。
これで終了です。これで以下のようにデバイスの詳細画面でステータスに時間が表示されば登録が完了したことが確認できます。
ES920LRTH2側がきちんと動作していればアプリケーション一覧の「データ」タブでpayloadが飛んでいることが確認できると思います。
4-2. 動作確認
上記手順でバイナリデータを受信できるようになりましたが、目で見えるようにPayloadのdecoder/converterを書き換えましょう。
function Decoder(bytes, port) {
// Decode an uplink message from a buffer
// (array) of bytes to an object of fields.
//var decoded = {};
var decoded = {
"Temperature": (bytes[1] << 8) + bytes[0],
"Humidity": (bytes[3] << 8) + bytes[2],
"Voltage": (bytes[5] << 8) + bytes[4]
};
// if (port === 1) decoded.led = bytes[0];
return decoded;
}
function Converter(decoded, port) {
// Merge, split or otherwise
// mutate decoded fields.
var converted = decoded;
// if (port === 1 && (converted.led === 0 || converted.led === 1)) {
// converted.led = Boolean(converted.led);
// }
roundup = function(value, digit) {
digits = Math.pow(10, digit);
return Math.round(value * digits) / digits;
}
converted.Temperature = -45 + (175 * converted.Temperature / (Math.pow(2, 16) - 1));
converted.Humidity = 100 * converted.Humidity / (Math.pow(2, 16) - 1)
converted.Voltage = 3.0 / (Math.pow(2, 12) - 1) * converted.Voltage;
converted.Temperature = roundup(converted.Temperature, 2)
converted.Humidity = roundup(converted.Humidity, 2)
converted.Voltage = roundup(converted.Voltage, 2)
return converted;
}
こうすることで、下記のようにアプリケーションデータで、センサー値を確認することができます。
(自分に向けて)お疲れさまでした😭
その他メモ
- Cayenneにつなげてみたかったが、TTNのconverterがバイナリデータを返せないので断念
- Acknowledgeの受け取り方が知りたい
主な参照元
-
Lora/GPS HAT
-
Use Lora/GPS HAT + RaspberryPi to set up a Lora Node
※この他にもいっぱいあったのですがメモし忘れました…
-
DRAGINO LoRa/GPS HAT v1.4-JPはシングルチャネルなので、fork元の https://github.com/tftelkamp/single_chan_pkt_fwd を利用すべきだと思いましたがすでにメンテナンスされていないのでこちらを使うのが正解みたいです ↩