STM32 DX-LR30 メッシュセンサーネットワーク
STM32F103C8 + DX-LR30 (SX1262) + BMI160 + ESP32-C6 による、Node / Repeater / Gateway 兼用のLoRaメッシュセンサーネットワーク一式。Gatewayはメッシュのデータを MQTTS (TLS) でMQTTブローカーへ転送し、Wi-Fi設定ポータル (APモード) で設定します。
参照元リポジトリ:
STM32 Radio, DX-LR20, SX1262 LORA Communication.
ディレクトリ構成
project/
├── platformio.ini # STM32側: CLI / mesh / mesh_debug の3ビルド環境
├── src/
│ ├── cli_main.cpp # 元のインタラクティブLoRa CLI(変更なし)
│ └── mesh_main.cpp # Node/Repeater/Gateway統合ファームウェア(本体)
└── esp32_gateway/ # ESP32-C6用の独立したPlatformIOプロジェクト (ESP-IDF)
├── platformio.ini
├── sdkconfig.defaults # TLS証明書バンドル、mainタスクのスタックサイズ
└── src/
├── main.cpp # Wi-Fi (STA)・SNTP・MQTTS publish/subscribe・STM32とのUART橋渡し
├── gw_prov.cpp # NVSへの設定保存 + APモードの設定ポータル (Webフォーム)
├── gw_prov.h
├── CMakeLists.txt # コンポーネントの依存関係定義
├── Kconfig.projbuild # ビルド時オプション (UARTピン、初期値、設定用AP など)
└── ca_cert.pem # (任意) 自己署名・自前CAを使う場合のみ
lib/SX1262_Custom/(sx1262_driver.h/.cpp)は元リポジトリのものをそのまま project/lib/SX1262_Custom/ にコピーして使用(本一式には含まれていません)。
ESP32-C6 Gateway
ESP32-C6は公式PlatformIOレジストリのespressif32プラットフォームで、 espidfフレームワークのみ サポート対象(arduinoフレームワークは未対応。 platformio/platform-espressif32 issue #1694 で報告されている既知の課題)。 そのため esp32_gateway は最初からESP-IDFで実装。
ESP32-C6はUSB Serial/JTAGを内蔵しているため、USB-Cケーブル1本で書き込み・ シリアルモニタ・JTAGデバッグ(VSCode+PlatformIOのDebug機能)が完結するため外付けのJTAGプローブは不要。
Gatewayの役割
起動直後からSTM32のPING (0xF0) にPONG (0xF1) で応答し、STM32にGatewayと判定させます。 UARTはWi-Fi/SNTPより先に 初期化するため、接続処理中や設定モード中でもPINGに応答できます。
STM32からUARTで受け取ったフレームを、TLSでMQTTにpublish。 結果バイト(0x01=成功 / 0x02=失敗)をSTM32へリターン。
リレー操作コマンドをsubscribeし、UARTでSTM32へ転送。
MQTTトピック
<prefix> は設定ポータルで指定するトピックプレフィックス(初期値はKconfig)。
方向
トピック
ペイロード
Publish
<prefix>/<node_id>/data
JSON: node_id, seq, event_type (heartbeat/motion), rssi, snr, hop_limit、存在する場合は ax〜gz
Publish
<prefix>/<node_id>/status
JSON: node_id, seq, commanded_on, feedback_pin_high, last_cmd_seq_applied, rssi, snr
Subscribe
<prefix>/+/cmd/relay
ON または 1 でオン、それ以外はオフ
publishはQoS 0です。プレフィックスは、先頭と末尾に / を付けず、# と + を含まず、 47文字以内にしてください。
設定方法: 設定ポータル (APモード)
Wi-FiとMQTTの設定はソースに埋め込まず、Gateway自身が提供するWebフォームから入力し、 NVSに保存。
設定モードに入る条件
きっかけ
動作
保存済みの設定がない(初回起動)
設定モードで起動
起動時にWi-Fi接続が30秒失敗
次回の起動で設定モードに入るよう再起動
動作中にBOOTボタン(初期値GPIO9)を5秒長押し
設定モードに入るよう再起動
動作中にWi-Fiが切れた場合は設定モードに入らず、そのまま再接続を繰り返す。
ポータルの使い方
スマホやPCでWi-Fi LoRa-GW-XXXX に接続します(XXXX はSoftAPのMACアドレス下位2バイト)。 パスワードは CONFIG_GATEWAY_PROV_AP_PASSWORD(初期値 gateway-setup。必ず変更してください )。
ブラウザで http://192.168.4.1/ へアクセス。キャプティブポータル用のDNSは実装していないため、 アドレスは手入力してください。スマホが「インターネットに接続されていません」と表示したら、 接続を維持する側を選択。
フォームに入力して 保存して再起動 を押下。Gatewayが再起動し、STAモードで動作開始。
項目
備考
SSID
設定モード開始時にスキャンした周辺のSSIDが候補として表示
Wi-Fiパスワード
空欄で保存すると現在の値を維持。保存済みの値がなく空欄の場合はオープンネットワーク扱い
ブローカURI
mqtts:// を指定。例: mqtts://mqtt.example.com:8884
MQTTユーザー名 / パスワード
パスワードを空欄にすると現在の値を維持
トピックプレフィックス
上記のトピックの規則を参照
設定ポータルに CONFIG_GATEWAY_PROV_TIMEOUT_SEC(初期値300秒)のあいだ操作がなければ、 Gatewayは再起動してSTAモードを試し直します(ルーターがまだ復旧していない停電復旧時でも、 自力で復帰できるようにするため)。
ビルド時オプション (Kconfig)
$ cd project/esp32_gateway
$ pio run -t menuconfig
# "Gateway Configuration" メニュー
オプション
用途
GATEWAY_UART_PORT_NUM, GATEWAY_UART_TX_PIN, GATEWAY_UART_RX_PIN
STM32とのUART接続
GATEWAY_MQTT_BROKER_URI
設定フォームに表示される初期値
GATEWAY_MQTT_TOPIC_PREFIX
設定フォームに表示されるプレフィックスの初期値
GATEWAY_NTP_SERVER
時刻同期に使うNTPサーバ(初期値 ntp.nict.jp)
GATEWAY_MQTT_USE_CUSTOM_CA
公的CAバンドルの代わりに ca_cert.pem を埋め込む
GATEWAY_PROV_AP_PASSWORD
設定用APのパスワード(8文字以上)
GATEWAY_PROV_BUTTON_GPIO
5秒長押しに使うボタン(初期値9)
GATEWAY_PROV_TIMEOUT_SEC
設定モードの無操作タイムアウト(初期値300)
ESP32-C6のGPIO9はストラップピン。起動後に押す分には問題ありませんが、 リセット中にLowにしているとROMのダウンロードモードに入るので注意。
sdkconfig.defaults
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144
最初の2行は、Let’s Encryptなど公的CAの証明書を検証するためのCAバンドルを有効にします。 スタックサイズについては、PlatformIOが「Default Value Mismatch」と通知しますが、 情報表示であり問題ありません。
src/CMakeLists.txt
idf_component_register(
SRCS "main.cpp" "gw_prov.cpp"
INCLUDE_DIRS "."
REQUIRES nvs_flash esp_wifi esp_event esp_netif esp_timer
esp_http_server mqtt json driver mbedtls
)
自前CAを使う場合は、esp32_gateway/platformio.ini に board_build.embed_txtfiles = src/ca_cert.pem も追加すること。
MQTTSとブローカーの設定
通信の保護とユーザー認証は、別の仕組み。
TLSのハンドシェイクでは証明書の有効期間を検証するため、GatewayはMQTT開始前にSNTPで時刻を合わせます。 URIのホスト名は、証明書のCN/SANと一致している必要があります(IPアドレスでは検証に失敗します)。
推奨構成: HAProxyでTLS終端
ESP32-C6 --TLS 8884--> HAProxy (TLS終端) --平文 1883--> Mosquitto (パスワード + ACL)
OpenRemote Proxyの既存の listen mqtt ブロックを参考にした例。自分用の haproxy.cfg にこのブロックを追加し、既存の 8883 のブロックはそのまま残してください。
haproxy
listen mosquitto_mqtts
bind *:8884 ssl crt /etc/haproxy/certs crt "${CERT_DIR}" crt "${CUSTOM_CERT_DIR}" no-tls-tickets
mode tcp
option clitcpka
timeout client 3h
timeout server 3h
option logasap
balance leastconn
server mosquitto mosquitto:1883 resolvers docker_resolver
yaml
services:
proxy:
ports:
- "8884:8884"
volumes:
- ./haproxy.cfg:/data/proxy/haproxy.cfg
environment:
HAPROXY_CONFIG: '/data/proxy/haproxy.cfg'
Mosquittoの1883はホストに未公開。Dockerネットワーク内で、 プロキシからのみ到達。
Mosquitto
mosquitto.conf
listener 1883 0.0.0.0
protocol mqtt
allow_anonymous false
password_file /mosquitto/config/passwd
acl_file /mosquitto/config/acl.conf
Gateway専用のユーザーを作成し(パスワードファイルが既にある場合は -c を付けない)、 ACLで権限を絞ります。lora はトピックプレフィックスに置き換えてください。
bash
$ docker compose exec mosquitto mosquitto_passwd -b /mosquitto/config/passwd gw01 'your-password'
conf
# /mosquitto/config/acl.conf
user gw01
topic write lora/+/data
topic write lora/+/status
topic read lora/+/cmd/relay
acl_file を有効にすると、ACLに書かれていないユーザーは何も読み書きできなくなります。 このブローカーを使う他のクライアントがあれば、そのエントリも追加してください。
PCからの接続テスト:
bash
$ mosquitto_pub -h mqtt.example.com -p 8884 --capath /etc/ssl/certs \
-u gw01 -P 'your-password' -t lora/1/cmd/relay -m ON
Gatewayが接続できないとき
MQTT_EVENT_ERROR のときに、シリアルログへ原因が出力されます。
ログ
考えられる原因
TLS/TCP error: esp_tls_err=... tls_stack_err=...
証明書の検証失敗、時刻が未同期、ホスト名の不一致、または8884へ到達できない
MQTT connection refused, return code=5
ユーザー名/パスワードの誤り
接続はできるがメッセージが届かない
ACLで拒否されている可能性(ACLの拒否はエラーにならず、publishは黙って捨てられる)。ACLのトピックとプレフィックスが一致しているか確認
Time synced が出ない
SNTPの同期に失敗。時刻が合うまで、証明書が「まだ有効でない」と判定されることがある
ビルド (STM32)
bash
# CLIデバッグビルド(動作確認・RF調整用)
$ pio run -e bluepill_cli -t upload
# 本番メッシュファームウェア
$ pio run -e bluepill_mesh -t upload
配線表
信号
STM32ピン
接続先
SPI1 NSS/SCK/MISO/MOSI
PA4/PA5/PA6/PA7
DX-LR30
USART1 (デバッグ、CLIビルドのみ)
PA9/PA10
USB-シリアル変換器
USART2 (ESP32通信)
PA2(TX)/PA3(RX)
ESP32-C6 UART(ピンはKconfigで設定。TX/RXはクロス接続)
I2C1 (BMI160)
PB6(SCL)/PB7(SDA)
BMI160モジュール
BMI160 電源
3.3V直結ピン
BMI160モジュールの3.3Vピン(VINではなくLDOバイパス側)
BMI160 INT1
PA1
BMI160モジュールのINT1
Repeater判定ジャンパー
PB0
GND短絡でRepeater、オープンでNode
ステータスLED
PB11
外付けの青色LED(アクティブHIGH配線。極性が逆ならLED_ACTIVE_HIGHを0に)
役割判定ロジック
ESP32がUARTのPING(0xF0)/PONG(0xF1)ハンドシェイクに応答 → Gateway
PB0がGNDに落ちている(ジャンパー装着) → Repeater
どちらでもない → Node (省電力・BMI160センサー)
起動時にLEDが役割に応じて点滅(1回=Node、2回=Repeater、3回=Gateway)。 Nodeにも関わらずBMI160が検出できなかった場合、5連の速い点滅でエラー表示し、 モーション検知なしのハートビート専用モードにフォールバック。