M5Stackでできること 〜Wi-FiとBLEの通信距離を比較する

屋外でIoTシステムを構築しようとすると、電源と通信手段が問題になります。

私は小さな畑で、IoTに関する色々な実験を行っていますが、そこでは電源としてソーラー発電を、通信手段としてモバイルWi-Fiルータを使っています。

モバイルWi-Fiルータは畑の真ん中あたりに常設しており、畑の色々な場所にあるIoTデバイスから、Wi-Fi経由でインターネットにデータを送信しています。

さて、Wi-Fi(を含む2.4GHz帯)には「金属や水分を通過すると通信距離が落ちる」「見通しが悪いと通信距離が落ちる」という特性があります。
私が利用している畑は里山地域にあり、形状が複雑な上、あまり手入れができていないため雑草が生え放題と、Wi-Fi通信にとってはあまり良い環境ではありません。

実際に、IoTデバイスとモバイルWi-Fiルータがそれ程離れていない状態でも、IoTデバイスからのデータがときどき届かなくなったりします。

そんな中、知り合いの方から、こちら の動画を紹介していただきました。

近距離通信に広く活用されている「BLE(Bluetooth Low Energy)」ですが、実はBLEでも結構遠くまで通信することができるという内容です。
障害物のない平地であれば、BT4.2で200メートル、BT5.Xならなんと1000mも通信できるとのことです。

M5Stack(ESP32)にはBLE(4.2)が搭載されており、追加コストなしでBLE通信が可能なので、もしもWi-FiよりもBLEの方が遠くまで通信できるとなれば、さまざまな用途に活用できそうです。
また、BLEはWi-Fiよりも低電力と言われていますので、電源が安定して確保できない屋外においては、たとえBLEとWi-Fiの通信距離が同等であっても、色々なIoTデバイスへの活用が考えられます。

そんな訳で今回、BLEとWi-Fiで、通信距離の比較を行ってみることにしました。

以下のような方法で調査を行いました。

  • M5StackにENV IIユニットをつなぎ、それを「センサ端末」とします。
  • センサ端末で「温度」と「湿度」のデータを取得し、まずはBLEの「ブロードキャストモード」で10秒間発信(アドバタイズ)します。
  • その後さらに10秒経ってから、今度はWi-Fiルータに接続し、同じデータをAmbientに送信します。
  • データ送信が完了したら、10秒経ってから30秒のディープスリープに移行します。
  • センサ端末では、この処理をずっと繰り返します。
  • BLEを受信する「Gateway」もM5Stackでつくります。
  • GatewayはあらかじめWi-Fiルータに接続しておきます。
  • センサ端末からBLEデータを受信したら、そこから「温度」と「湿度」のデータを取り出し、Wi-Fiルータ経由でAmbientに送信します。

よって、1分ちょっと毎に、以下の2種類の経路で、同じデータがAmbientに送信されることになります。

  • センサ端末 →(BLE)→ BLE Gateway →(WI-Fi)→ モバイルWi-Fiルータ →(モバイル回線)→ Ambient
  • センサ端末 →(WI-Fi)→ モバイルWi-Fiルータ →(モバイル回線)→ Ambient

「センサ端末」のスケッチは以下のとおりです。
BLE通信の処理は、こちら の記事に記載されている内容をほぼそのまま使わせていただきました。

#include <M5Stack.h>
#include "SHT3X.h"
#include "BLEDevice.h"
#include "BLEServer.h"
#include "BLEUtils.h"
#include "Ambient.h"

SHT3X sht30;
WiFiClient client;
Ambient ambient;

RTC_DATA_ATTR uint8_t seq;
float tmp = 0.0;
float hum = 0.0;

const char*  ssid      = "XXXXXXXX";
const char*  password  = "XXXXXXXX";
unsigned int channelId = XXXXX;
const char*  writeKey  = "XXXXXXXX";

void setAdvData(BLEAdvertising *pAdvertising) {
  uint16_t temp = tmp * 100;
  uint16_t humi = hum * 100;

  BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
  std::string strServiceData = "";
  strServiceData += (char)0x0a;
  strServiceData += (char)0xff;
  strServiceData += (char)0xff;
  strServiceData += (char)0xff;
  strServiceData += (char)seq;
  strServiceData += (char)(temp & 0xff);
  strServiceData += (char)((temp >> 8) & 0xff);
  strServiceData += (char)(humi & 0xff);
  strServiceData += (char)((humi >> 8) & 0xff);
  oAdvertisementData.addData(strServiceData);
  oAdvertisementData.setFlags(0x06);
  pAdvertising->setAdvertisementData(oAdvertisementData);
}

void setup() {
  M5.begin();
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(0, 0);
  esp_sleep_enable_timer_wakeup(30 * 1000 * 1000); // 30秒
  M5.Lcd.println("BLE & WiFi Sensor Device");

  // Sensor
  if(sht30.get()==0) {
    tmp = sht30.cTemp;
    hum = sht30.humidity;
  }
  M5.Lcd.printf("seq:%d tmp:%2.1f hum:%2.0f%%\n", seq, tmp, hum);

  // BLE
  BLEDevice::init("M5Stack-BLE");
  BLEServer *pServer = BLEDevice::createServer();
  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  setAdvData(pAdvertising);
  pAdvertising->start();
  M5.Lcd.println("BLE start...");
  delay(10000);
  pAdvertising->stop();
  M5.Lcd.println("BLE stop...");
  delay(10000);

  // Wi-Fi
  M5.Lcd.print("WIFI START ");
  unsigned long wifiCnt = 0;
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED) {
    wifiCnt++;
    delay(500);
    M5.Lcd.print(".");
    if(wifiCnt%10 == 0) {
      WiFi.disconnect();
      WiFi.begin(ssid, password);
    }
    if(wifiCnt >= 30) {
      M5.Lcd.println(" - FAIL");
      esp_deep_sleep_start();
    }
  }
  M5.Lcd.println(" - READY");

  ambient.begin(channelId, writeKey, &client);
  ambient.set(1, tmp);
  ambient.set(2, hum);
  ambient.set(3, seq);
  ambient.send();
  delay(10000);

  seq++;
  esp_deep_sleep_start();
}

void loop() {
}

「BLE Gateway」のスケッチは以下のとおりです。
このスケッチについても、こちら の記事に記載されている内容をほぼそのまま使わせていただきました。

#include <M5Stack.h>
#include "BLEDevice.h"
#include "Ambient.h"

WiFiClient client;
Ambient ambient;
BLEScan* pBLEScan;

uint8_t seq;
#define MyManufacturerId 0xffff

const char*  ssid      = "XXXXXXXX";
const char*  password  = "XXXXXXXX";
unsigned int channelId = XXXXX;
const char*  writeKey  = "XXXXXXXX";

void setup() {
  M5.begin();
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("BLE Gateway");

  M5.Lcd.print("BLE SCAN START");
  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan();
  pBLEScan->setActiveScan(false);
  M5.Lcd.println(" - READY");

  M5.Lcd.print("WIFI START ");
  unsigned long wifiCnt = 0;
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED) {
    wifiCnt++;
    delay(500);
    M5.Lcd.print(".");
    if(wifiCnt%10 == 0) {
      WiFi.disconnect();
      WiFi.begin(ssid, password);
    }
    if(wifiCnt >= 30) {
      M5.Lcd.println(" - FAIL");
      ESP.restart();
    }
  }
  M5.Lcd.println(" - READY");

  ambient.begin(channelId, writeKey, &client);
}

void loop() {
  bool found = false;
  float tmp, hum;

  BLEScanResults foundDevices = pBLEScan->start(3);
  int count = foundDevices.getCount();
  for(int i=0; i<count; i++) {
    BLEAdvertisedDevice d = foundDevices.getDevice(i);
    if(d.haveManufacturerData()) {
      std::string data = d.getManufacturerData();
      int manu = data[1] << 8 | data[0];
      if (manu == MyManufacturerId && seq != data[2]) {
        found = true;
        seq = data[2];
        tmp = (float)(data[4] << 8 | data[3]) / 100.0;
        hum = (float)(data[6] << 8 | data[5]) / 100.0;
        M5.Lcd.fillScreen(BLACK);
        M5.Lcd.setCursor(0, 0);
        M5.Lcd.printf("seq:%d tmp:%2.1f hum:%2.0f%%\n", seq, tmp, hum);
      }
    }
  }

  if(found) {
    ambient.set(1, tmp);
    ambient.set(2, hum);
    ambient.set(3, seq);
    ambient.send();
  }
}

さて、準備ができましたので、実際に畑で実験してみます。

「BLE Gateway」にモバイルバッテリーをつなぎ、袋に入れて、モバイルWi-Fiルータを設置しているのと同じ支柱に括り付けます。
つまり、センサ端末から見ると、モバイルWi-FiルータとBLE Gatewayは、ほぼ同じ場所にあることになります。

「センサ端末」にもモバイルバッテリーをつなぎ、袋に入れ、鍬の持ち手部分に括り付けます。

この鍬を畑のいろいろな場所に持っていき、そこから通信できるかどうかを確認します。

「センサ端末」ー「BLE GatewayとモバイルWi-Fiルータ」間の距離を測定する良い方法を思いつかなかったので、今回はスマホのGPS測位アプリを利用しました。
半径5メートルの水平精度とのことですので、2点で合わせて10メートルの誤差があることになります。

まずは、既にIoTデバイスを設置し、Wi-Fi経由でデータ送信もしている場所で調査を行います。
2点間の距離は約21メートル、間に雑草は生えていますが、同一の畑の中であり、見通しは良好です。

この場所での調査の結果、

  • Wi-Fi:30分間に26回データ送信(1分4秒〜1分21秒間隔)
  • BLE:30分間に26回データ送信(1分3秒〜1分19秒間隔)

と、どちらも問題なく通信できました。

次に、少し離れた場所で調査を行います。
センサ端末を設置したのは、畑より1段上の田んぼです。2点間の距離は約43メートル、10メートル弱の高低差もあり、わずかに相手方を視認することはできますが、間は草に覆われています。

この場所での調査の結果、

  • Wi-Fi:30分間に24回データ送信(1分5秒〜2分17秒間隔)
  • BLE:30分間に7回データ送信(57秒〜10分11秒間隔)

となり、Wi-Fiでは概ねデータ送信できましたが、BLEでは半分以下しか送信できませんでした。
BLEでは、この程度の距離でも厳しいようです。

一応、さらに離れた場所でも調査してみました。
同じ田んぼのさらに奥にセンサ端末を設置しました。2点間の距離は約59メートル、先ほどセンサ端末を設置した場所とは同じ田んぼの中で、その間には障害物はありません(田んぼには水が張ってあります)。

この場所での調査の結果、

  • Wi-Fi:30分間に25回データ送信(1分7秒〜1分24秒間隔)
  • BLE:30分間に0回データ送信

と、BLEでは全くデータ送信できなくなりましたが、Wi-Fiでは送信できました。

今回の調査では、元々の認識どおり、Wi-Fiの方が遠くからでも通信できるという結果になりました。
先ほどの動画でも、BLEで遠距離通信するためには「アンテナのノウハウが肝心」と言われており、M5StackのハードウェアのままではBLEの能力を発揮できないのかもしれません。

ただ、今回は調査していませんが、IoTデバイスの低電力化のためには、確かにBLEは効果がありそうです。通信距離の制約がない用途で、BLEの活用策も考えてみたいと思います。

なお、私がM5Stack、M5StickCの使い方を習得するのにあたっては、以下の書籍を参考にさせていただきました。


ごく基本的なところから、かなり複雑なスケッチや、ネットワーク接続など、比較的高度なものまで、つまづかずに読み進めていけるような構成になっており、大変わかりやすい本です。


このサイトで書いている、M5Stackシリーズ(M5Stack、M5StickCなど)に関するブログ記事を、「さとやまノート」という別のブログページに、あらためて整理してまとめました。

他のM5Stackシリーズの記事にも興味のある方は「さとやまノート」をご覧ください。