FICUSONLINE F9E
LoRaネットワーク+OpenRemoteによるフィールドオートメーションシステム
LoRaによるネットワーク構築の最大のメリットは、通信事業者との契約や月額通信料金が不要で、独自の通信ネットワークを構築・運用できることです。また、見通しの良いフィールドであれば、約8kmの通信距離を確保できる場合もあります。 こうした特性を活かし、工場、駐車場、農地などの広範囲なフィールドにおいて、盗難防止やセキュリティ対策をはじめ、給水設備や照明などの遠隔監視・制御にも活用できます。
Takanobu FuseAdministrator

2 days ago

Hardware

フィールドオートメーションシステム

はじめに

920 MHz帯のLoRaを使い、電池駆動のセンサーとリレー制御を一つのメッシュネットワークにまとめ、OpenRemoteで一元管理・制御するシステムを構築。 システムを構成するデバイスの役割、通信経路、OpenRemote側との接続方法などをソースコードを交え説明します。

システムを構成するデバイスには共通して、STM32F103C8(Blue Pill)と DX-LR30 を採用しており、ファームウェアを共通化。デバイスの役割は、ジャンパ配線と追加部品、ESP32の有無から起動時に自動判定されます。

また、リレー指令(Gateway→Actuator)は、SipHash-2-4による署名と、Actuatorが発行する使い捨てのnonceで認証しています。詳細は「コマンド認証」の節にまとめました。

システム全体像

Field Automation System with LoRa and OpenRemote

  • 上り:センサーのデータは、必要に応じてRepeaterを経由してGatewayに届き、ESP32-C6がMQTTSでブローカーへ発行。
  • 下り:ブローカーに発行されたリレー指令をGatewayが受け取り、LoRaでActuatorへ送信。

共通ハードウェア

全てのデバイスに共通する部分は次の通りです。

部品 役割
STM32F103C8(Blue Pill) メッシュ制御、スリープ管理
DX-LR30(SX1262) 920 MHz LoRa送受信

RF設定は 920 MHz、SF9、BW 125 kHz、CR 4/5、+13 dBm で、全デバイスで同一。

デバイスの種類と役割

追加する部品や配線によって、デバイスの役割を決定。

役割 追加するもの 決まり方 用途
Node BMI160(加速度・ジャイロ) ジャンパなし(デフォルト) 電池駆動のセンサー
Repeater なし PB0 → GND 中継
Actuator リレー、フィードバック入力 PB1 → GND リレー出力と状態通知
Gateway ESP32-C6 起動時のUART PINGにESP32が応答 メッシュとMQTTの橋渡し

Node(センサーノード)

I2C接続が可能で電池駆動に適したセンサモジュールを取り付けます。今回は手動でデータ取得頻度を上げることができる加速度センサモジュールBMI160を接続しました。センサの種類に応じてコードは書き換えて下さい。 動作仕様として、通常はSTOPモードで待機し、BMI160のAny-Motion割り込み(INT1)または定期ハートビートで起床、ACKとリトライ付きで送信します。ハートビート間隔の初期値は3600秒。 センサーノードは送信直後にしか受信できないため、下りのコマンドには非対応。

Repeater(中継器)

常時受信で、センサーパケット(MeshPacket)をホップ数制限と重複抑制つきで中継しますが、リレー指令とそのステータス応答を中継しません。 そのためActuatorはGatewayの直接通信圏内に置く必要があります。

Actuator(リレー出力)

常時受信で、リレー出力(PB4)と物理状態のフィードバック入力(PB5)を持ちます。ステータスは次の2つの値を独立して報告。

  • commandedState:Gatewayから指示された出力レベル
  • feedbackPinState:フィードバックピンの実際のHIGH/LOW

Actuatorは、受信したコマンドのタグとnonceを検証し、どちらも正しい場合だけリレーを動かします。ステータスには、次のコマンドに必要な使い捨てのnonceも載せます。

Gateway(ゲートウェイ)

STM32+DX-LR30にESP32-C6を接続した構成。STM32がメッシュのパケットを受信してACKを返し、UART(CRC8付きフレーム)でESP32-C6へ転送。ESP32-C6側はESP-IDFで実装されており、次を担当。

  • Wi-Fi接続とSNTPによる時刻同期
  • MQTTS(TLS)でのPublish / Subscribe
  • STM32とのUARTブリッジ
  • Actuatorのnonceの保持と、リレー指令への署名(SipHash-2-4)

結線は、STM32のPB10(TX)/PB11(RX)をESP32-C6のUARTにクロス接続し、GNDを共通。リセット線は接続しません。

リレー指令は、MQTTから受け取ったESP32-C6が、Actuatorのnonceとタグを付けたフレームにして、UARTでSTM32へ渡します。STM32は署名も検証もせず、そのままLoRaに載せて転送します。

OpenRemoteとの接続

OpenRemoteのDockerコンテナによる運用については、別途ブログでまとめます。

MQTTトピック設計

<prefix> はセットアップポータルで設定するトピックプレフィックスです。

方向 トピック 内容
Publish <prefix>/<node_id>/data ハートビート/モーションのJSON(RSSI、SNR、加速度・ジャイロなど)
Publish <prefix>/<node_id>/status commanded_on、feedback_pin_high、last_cmd_seq_applied など
Subscribe <prefix>/+/cmd/relay ON または 1 でON、それ以外はOFF

メッセージはQoS 0で発行。

last_cmd_seq_applied は、適用されたコマンドのnonceです。コマンドが適用されるたびに値が変わります。

ブローカー構成

OpenRemoteと同じホスト内に用意したMosquittoを使い、TLSはHAProxyで終端する構成にしています(全てDockerコンテナ)。

ESP32-C6 --TLS 8884–> HAProxy (TLS終端) --平文 1883–> Mosquitto (パスワード認証 + ACL)

OpenRemote標準の8883のブロックには手を加えず、haproxy.cfg に8884用の listen ブロックを追加します。 公的CA(Let’s Encrypt等)の証明書ならESP32側の内蔵CAバンドルで検証できるため、デバイスに証明書ファイルを置く必要はなく、証明書更新時のファーム更新も不要です。 注意点は次のとおりです。

  • TLS検証には有効期限の確認が含まれるため、MQTT開始前にSNTPで時刻を同期。
  • ブローカーのホスト名は証明書のCN/SANと一致させます(IPアドレスでは検証できません)。
  • Mosquittoの1883番ポートはホストに公開せず、Dockerネットワーク内でプロキシからのみ到達できるようにします。

Gateway専用のMosquittoユーザーを作り、ACLで必要最小限に絞ります。

user gw01
topic write lora/+/data
topic write lora/+/status
topic read  lora/+/cmd/relay

コマンド認証(SipHash-2-4 + nonce)

LoRa区間そのものには、暗号化も認証もありません。何も対策しないと、電波の届く範囲の第三者が、リレー指令を偽造したり、盗聴した指令を再送したりできてしまいます。 そこで、Gateway→Actuatorのリレー指令に、署名とnonceによる認証を入れました。

仕組み

DATA Flow

  1. Actuatorは、使い捨てのnonce(4バイト)を持ち、すべてのステータス(StatusPacket)に載せて送ります。
  2. ESP32-C6は、ステータスから、Actuatorごとの最新のnonceを覚えます。
  3. MQTTの cmd/relay を受け取ると、ESP32-C6が、覚えているnonceを入れて、SipHash-2-4で署名(8バイトのタグ)し、UARTでSTM32に渡します。
  4. STM32 Gatewayは、署名も検証もせず、そのままLoRaで送ります。
  5. Actuatorは、タグが正しく、nonceが現在のものと一致する場合だけ、リレーを動かします。直後にnonceを新しくして、結果のステータスと一緒に知らせます。

署名の対象は、magic0 から cmdValue までの10バイト(リトルエンディアン)です。宛先、nonce、コマンドの値が含まれるので、どれを書き換えても、タグが合わなくなります。

パケットサイズ

区間 サイズ
UART下り(ESP32→STM32) 19バイト: CC 33 target(2) type value nonce(4) tag(8) CRC8(CRC8の対象は target から tag まで)
LoRa CmdPacket 19バイト: tag[8] を追加。cmdSeq 欄には、Actuatorが発行したnonceを入れる
LoRa StatusPacket 19バイト: nonce(4バイト)を追加

nonceの更新

Actuatorは、次の3つのタイミングで、nonceを新しくします。

  • コマンドを適用した後(結果のステータスと同時)
  • 60秒ごとの定期ステータスの直前
  • 起動時の最初のステータスの直前

nonceは、前回のnonce、カウンタ、micros()、RSSI、ADCの値を、共有鍵つきでハッシュして生成されます。

効果

  • 盗聴した「ON」を、OFFにした後で再送しても、適用されません。nonceが、すでに更新されているためです。
  • コマンドが適用された直後に、同じパケットが再送されても(リトライ)、再適用されません。ステータスだけが返り、Gatewayは結果を確認できます。
  • 署名済みで、届かなかったコマンドは、最長60秒で失効。
  • 値、宛先、タグの改変や、鍵の違いは、すべて拒否されます。

鍵の管理

署名には、16バイトの共有鍵を使います。ESP32-C6とActuatorで、同じ値に揃えます。

  • STM32側:ビルド時に stm32_dx-lr30/src/mesh_secret.h で埋め込みます。このファイルはコミットしません(.gitignore に追記)。
  • ESP32-C6側:セットアップポータルの「Relay command key」に、32文字のhexを入力し、NVSに保存します。ファームウェアにも、sdkconfig にも入りません。
  • 鍵の作成:gen_mesh_key.py が、乱数で鍵を作り、mesh_secret.h を書き出し、ESP32-C6に入れるhexを表示します。
$ python3 gen_mesh_key.py          # 新しい鍵を作る(すでにあれば上書きしない)
$ python3 gen_mesh_key.py --force  # 鍵を作り直す(ESP32と全STM32を更新し直す)

ESP32-C6に鍵が未設定、または不正な場合は、リレー指令を無効にして、ログに案内を出します。センサーデータの転送は、通常どおり動きます。

Gatewayのセットアップ

Wi-FiやMQTTの設定はファームウェアに埋め込まず、GatewayがAPモードで提供するWebフォームから入力し、NVSに保存。

  1. 初回起動、起動時のWi-Fi接続が30秒失敗したとき、または実行中にBOOTボタンを5秒長押ししたときに、セットアップモードに入ります。
  2. スマートフォンやPCで LoRa-GW-XXXX に接続します(パスワードはKconfigの設定値。初期値は変更してください)。
  3. http://192.168.4.1/ を開きます。キャプティブポータルのDNSはないため、アドレスは手入力です。
  4. SSID、Wi-Fiパスワード、mqtts:// で始まるブローカーURI、MQTTのユーザー名とパスワード、トピックプレフィックス、リレー指令用の鍵(32文字のhex)を入力し、保存して再起動します。

パスワード類と同じく、鍵の欄も空欄のままなら、保存済みの値を維持します。32文字のhex以外は、保存できません。ポータルは、WPA2のAPの上の平文HTTPなので、セットアップは、周囲に他人がいない状況で行ってください。

実行中にWi-Fiが切れてもセットアップモードには入らず、再接続を試み続けます。

現時点の制限と注意点

  • 無線は半二重のため、Gatewayが送信中やActuatorの応答待ちの間にパケットを取りこぼすことがあります(Node側のACK/リトライで回復します)。
  • MQTTはQoS 0で、切断中のデータは破棄されます(蓄積転送なし)。Gatewayがオフラインの間に発行した指令は失われ、Last Willによる死活トピックもありません。
  • 認証が及ぶのは、Gateway→Actuatorのリレー指令だけです。センサーデータ(上り)とActuatorのステータスには署名がなく、偽造される可能性があります。また、暗号化はしていないので、内容は電波を傍受すれば読めます。
  • 鍵は、NVSに平文で保存されます(NVS暗号化を有効にしない限り、基板を入手した人は読み出せます)。STM32側は、全デバイスが同じファームウェアなので、すべてのデバイスのFlashに同じ鍵が入ります。1台を入手されると、全体の鍵が分かります。
  • ESP32-C6の再起動直後や、コマンドが失敗した直後は、Actuatorのnonceが分からないので、次のステータス(最長60秒)まで、指令が保留されます。通常は、結果のステータスで次のnonceが届くので、連続操作は、すぐに通ります。
  • 認証を入れても、施錠や、安全に関わる負荷、無人での商用電源の開閉には、使わないでください。
  • 920 MHz帯は法規制の対象です(日本ではARIB STD-T108:認証済みモジュール、キャリアセンス、送信時間制限)。地域の規則を確認してください。

詳細について

下記URLを参照願います。

https://forum.ficusonline.com/t/topic/568/14

ソースコード:GitHub

https://github.com/capitalfuse/lora-project