【重要】LINBLE-Z2量産販売開始およびLINBLE-Z1使用部品変更予定のお知らせ

M5StackでBluetooth® LE通信を実現するまでの手順メモ⑥センサデータをアドバタイズ発信編

こんにちは、ムセンコネクトCCOの伊藤です。(プロフィール紹介はこちら

M5StackでBluetooth® LE(BLE)通信を実現するまでの手順メモ」の第6回目です。今回は「センサデータをアドバタイズ発信編」です。

ご注意ください

M5Stack自体はBluetooth認証を取得していないため、M5Stackで開発した最終製品のBluetooth認証取得は困難な場合があります。M5Stackを用いた製品化をご検討の際は、この点に十分ご注意ください。

M5Stackの公式サイトで製品を検索しても「Bluetooth」というキーワードでは見つからない
目次

センサーを接続して値を取得する

M5Stackは、様々なセンサーをはじめ周辺機器を気軽に試せる環境が整っています。それらの周辺機器はUnitと分類されて、データシートやサンプルコードなども公開されています。

今回は、社内に眠っていたカラーセンサー(Unit Color )を見つけましたので、このセンサーを活用してみたいと思います。

カラーセンサーとCoreS3 SE

M5Stackは、IoT機器向けのコネクタ規格である Grove Port をサポートしており、M5Stack標準で搭載されている赤色のPort.A(I2C)コネクタを接続することで動作させることができるようです。

CoreS3-SEのGrovePort対応

赤色のPort.A(I2C)のほかに、拡張することでPort.B(GPIO)、Port.C(UART)にも対応可能です。規格で統一されているとプロトタイプ開発がしやすいですね。

また、サンプルコード(https://github.com/m5stack/M5Stack/blob/master/examples/Unit/COLOR_TCS3472/COLOR_TCS3472.ino )が公開されてはいますが、あえてこちらは試さず、今回もAIであるGemini 2.5 Proを利用してコーディングの支援をしてもらいます。繰り返しになりますが、AIの生成コードは過信しすぎないようにしましょう。

プロンプト指示(まずはセンサーを動かす)

まずは、本シリーズの中でセンサーを動作させることが初めてなので、一度Bluetooth LE(BLE)通信から離れて、センサー単体の動作確認を行います。

M5Stackにはディスプレイが搭載されていますので、取得したセンサー値をディスプレイ表示して目視確認ができる程度の簡単な動作で済ませたいと思います。

仕様として与えたプロンプトは以下のとおりです。

M5CoreS3 SE、M5Stack用カラーセンサユニット(TCS3472搭載)が手元にある。
一度Bluetooth LE通信から離れ、カラーセンサの動作確認を行いたい。

M5Stack用カラーセンサユニット(TCS3472搭載)の詳細は以下のとおりである。
https://docs.m5stack.com/en/unit/color

仕様を以下に示す。
仕様を満たすプログラムコードを提示してほしい。

開発環境
・Arduino IDE

目的
・M5Stack用カラーセンサユニット(TCS3472搭載)の動作を確認する

機能
1.センサデータ取得
・M5StackのPortAに接続する
・電源投入直後から1秒毎にセンシングする

2.ディスプレイ表示
・センシングした値に合わせてディスプレイの色を動的に変更する
・Clear、Red、Green、Blueそれぞれの値をディスプレイにテキスト表示する

Arduinoスケッチ(プログラムコード)が生成されるだけでなく、プログラムを動作させるためのライブラリについても指南してくれました。センサー型番(TCS3472)を記載したのは、I2Cの制御周りのコーディングのためでしたが、そもそもライブラリが用意されているようでした。

ご提示の仕様を満たす、M5CoreS3 SE用のArduinoプログラムを以下に示します。 
このプログラムを利用するには、事前にArduino IDEのライブラリマネージャから
Adafruit TCS34725ライブラリをインストールしてください。

そのままコンパイル実行するとエラーが発生したため、エラーコードをプロンプトとして与えました。

さらに、RGB値の算出方法について詳細にコメントを記載してもらうように指示し、最終的に生成されたArduinoスケッチ(プログラムコード)は以下のとおりです。

/**
 * @file M5CoreS3_Color_Sensor_Test.ino
 * @author Gemini
 * @brief M5CoreS3 SEとカラーセンサユニット(TCS3472)の動作確認プログラム
 * @version 1.2
 * @date 2025-07-31
 * @copyright Copyright (c) 2025
 *
 * @note
 * v1.2: データ変換処理のコメントを、TCS3472のデータシートの考え方を
 * 参考に、より詳細で正確な記述に修正。
 * v1.1: color888関数に非対応の引数(アルファ値)を渡していたことによる
 * コンパイルエラーを修正。
 *
 * このプログラムを使用する前に、Arduino IDEのライブラリマネージャで
 * "Adafruit TCS34725" を検索し、インストールしてください。
 *
 * 機能:
 * 1. PortAに接続されたカラーセンサから1秒毎にデータを取得する。
 * 2. 取得した色をディスプレイの背景色に反映させる。
 * 3. Clear, Red, Green, Blueの各値をディスプレイに表示する。
 */


#include <M5Unified.h>
#include <Wire.h>
#include "Adafruit_TCS34725.h"


// カラーセンサのインスタンスを作成
// 引数なしの場合、デフォルトの積分時間とゲインが設定されます
Adafruit_TCS34725 tcs = Adafruit_TCS34725();


// 最後にセンサを読み取った時刻を保存する変数
unsigned long lastReadTime = 0;


/**
 * @brief 初期設定
 */
void setup() {
  // M5Stackの初期化
  M5.begin();
 
  // シリアル通信の開始
  Serial.begin(115200);


  M5.Display.setTextSize(2);
  M5.Display.println("Color Sensor (TCS3472) Test");


  // カラーセンサの初期化
  if (tcs.begin()) {
    Serial.println("Found color sensor");
    M5.Display.println("Found color sensor");
  } else {
    Serial.println("No TCS3472 found ... check connections");
    M5.Display.println("No TCS3472 found...");
    while (1); // センサが見つからない場合はここで停止
  }


  // 画面を一度クリア
  delay(1000);
  M5.Display.fillScreen(BLACK);
}


/**
 * @brief メインループ
 */
void loop() {
  // 1秒ごとに処理を実行
  if (millis() - lastReadTime > 1000) {
    lastReadTime = millis(); // 時刻を更新


    uint16_t r, g, b, c; // 各色の値を格納する変数 (16bit)


    // センサから生データを取得
    tcs.getRawData(&r, &g, &b, &c);


    // -----------------------------------------------------
    // 16bit生データ(RGBC)をディスプレイ用の24bit RGBカラーに変換
    // -----------------------------------------------------
    // TCS3472は、Red, Green, Blue, Clear(フィルターなしの光量)の4つの値を
    // 16bitの解像度で出力します。
    //
    // そのまま表示すると、全体の明るさによって色が大きく変わってしまいます。
    // そこで、Clear値を全体の明るさ(1.0)とみなし、各色(R,G,B)が
    // どれくらいの割合を占めるかを計算(正規化)します。
    //
    // 計算式: (各色の値 / Clear値)
    //
    // これにより、照明の強弱に影響されにくい、安定した色表現が得られます。


    // C (Clear) の値が0の場合は除算エラーを避ける
    if (c == 0) {
      return;
    }


    // 各色の割合を計算し、ディスプレイで扱える8bit(0-255)の階調に変換します。
    // 3つの8bit値を組み合わせることで、最終的に24bitのRGBカラーとなります。
    uint8_t red   = (uint8_t)min(255.0f, ((float)r / c) * 255.0f);
    uint8_t green = (uint8_t)min(255.0f, ((float)g / c) * 255.0f);
    uint8_t blue  = (uint8_t)min(255.0f, ((float)b / c) * 255.0f);


    // 変換した色でディスプレイの背景を塗りつぶす
    M5.Display.fillScreen(M5.Display.color888(red, green, blue));


    // -----------------------------------------------------
    // 各値をディスプレイにテキスト表示する
    // -----------------------------------------------------
    // テキストの背景を不透明の黒にして、可読性を上げる
    M5.Display.fillRoundRect(10, 10, 300, 130, 8, BLACK);
   
    // テキストの色を白に設定
    M5.Display.setTextColor(WHITE);
    M5.Display.setTextSize(2);
   
    // 各値を表示
    M5.Display.setCursor(20, 20);
    M5.Display.printf("Clear: %u\n", c);
    M5.Display.setCursor(20, 50);
    M5.Display.printf("Red:   %u\n", r);
    M5.Display.setCursor(20, 80);
    M5.Display.printf("Green: %u\n", g);
    M5.Display.setCursor(20, 110);
    M5.Display.printf("Blue:  %u\n", b);
  }
}

修正してもらったことで、コンパイルエラーがなくなりCoreS3 SEに書き込みができました。また、RGB値の算出部分について理解を深めることもできました。

動作を確認する①

さて、書き込みが出来たので動作を確認していきます。カラーセンサーにはLEDがついており、光を当ててセンシングするようですね。ただ、光量が少なく、かつ、温白色のような色味なので、正しくセンシングできるのか少し不安に感じました。

対象物が無い状態のカラーセンサーとディスプレイ表示

対象物自身が発光している必要がありそうですので、社内に眠っていた積層信号灯を持ってきました。それぞれを点灯させ、センシングしてみます。

動画:m5stack-blog6-2.mp4

黄色の点灯部分が橙色に近い表示になっていてイマイチですが、それでも概ね良好な結果と言えるのではないでしょうか!?

プロンプト指示(アドバタイズデータに載せて発信する)

さて、センサーも動かすことができたので、ここからが本番です。

カラーセンサーのRed、Green、Blue、Clear値をアドバタイズデータに載せて発信することにします。ブロードキャスターとして発信さえしてしまえば、オブザーバーがどうとでも扱うことができます。

Apple社がiBeaconのフォーマットを提唱しているように、ムセンコネクトでは「オープンセンササービス」としてビーコンフォーマットを規定し、Service Data 16bit UUIDを自由に使ってよいと公表しています。

今回はオープンセンササービスのビーコンフォーマットに則って、カラーセンサー値を発信しようと思います。

温湿度、照度など一般的なセンサーはデータ種別として規定していますが、残念ながらカラーセンサーはありませんので、「符号なし2 バイトリスト」として扱うことにします。

もともと機能仕様としてプロンプト指示していましたので、機能追加部分を与えることにします。

与えたプロンプトは以下のとおりです。

無事、M5CoreS3 SE、M5Stack用カラーセンサユニット(TCS3472搭載)で、期待する動作を確認することができました。
仕様に、以下の機能を追加したいと思います。

機能
3.BLEアドバタイズ発信
・取得したカラーセンサーのRed、Green、Blue、Clearそれぞれの値をアドバタイズデータに載せて発信する
・1秒毎に取得したカラーセンサー値でアドバタイズデータを更新する
・アドバタイズ周期は100msとする
・アドバタイズ種別はADV_NONCONN_INDとする
・送信出力(TxPower)は日本国内で設定可能な最大値とする
・AdvDataは2つのAD Structureで構成し、以下のとおりとする。
 1)AD Type:0x01(Flags)
 Flag:0x06を指定

 2)AD Type:0x16(Service Data - 16-bit UUID)
 16bit UUID:0xFCBE(リトルエンディアンで指定が必要な場合は注意すること)
 以降のデータは
 010000000030000152RRRR52GGGG52BBBB52CCCC(16進数)とする
 RRRR:Red値(16bit/ビッグエンディアン)
 GGGG:Green値(16bit/ビッグエンディアン)
 BBBB:Blue値(16bit/ビッグエンディアン)
 CCCC:Clear値(16bit/ビッグエンディアン)
 をそれぞれ代入する
 
 これは、(株)ムセンコネクトが提唱しているオープンセンサーサービスを活用しています。
 https://www.musen-connect.co.jp/blog/course/product/howto-16bituuid-ble-beacon-open-sensor-service/

 オープンセンサーサービスの仕様書です。
 必要に応じて確認してください。
 https://www.musen-connect.co.jp/dl/open_sensor_beacon_spec.pdf

何度かコンパイルエラーが発生したため、プロンプトでやりとりをして最終的に生成されたArduinoスケッチ(プログラムコード)は以下のとおりです。

/**
 * @file M5CoreS3_Color_Sensor_Test.ino
 * @author Gemini
 * @brief M5CoreS3 SEとカラーセンサ(TCS3472)の値を取得し、BLEアドバタイズするプログラム
 * @version 2.3
 * @date 2025-08-01
 * @copyright Copyright (c) 2025
 *
 * @note
 * v2.3: setServiceDataの引数の型が異なっていたことによるコンパイルエラーを修正。
 * v2.2: BLEUUIDのコンストラクタ呼び出しが曖昧(ambiguous)であることによる
 * コンパイルエラーを修正。
 * v2.1: オープンセンサーサービスの仕様に関するコメントの誤りを修正。
 * v2.0: カラーセンサで取得した値をBLEアドバタイズする機能を追加。
 * v1.2: データ変換処理のコメントを、TCS3472のデータシートの考え方を
 * 参考に、より詳細で正確な記述に修正。
 * v1.1: color888関数に非対応の引数(アルファ値)を渡していたことによる
 * コンパイルエラーを修正。
 *
 * このプログラムを使用する前に、Arduino IDEのライブラリマネージャで
 * "Adafruit TCS34725" を検索し、インストールしてください。
 *
 * 機能:
 * 1. PortAに接続されたカラーセンサから1秒毎にデータを取得する。
 * 2. 取得した色をディスプレイの背景色に反映させる。
 * 3. Clear, Red, Green, Blueの各値をディスプレイに表示する。
 * 4. 取得したRGBC値をオープンセンサーサービスのフォーマットでBLEアドバタイズする。
 */


#include <M5Unified.h>
#include <Wire.h>
#include "Adafruit_TCS34725.h"
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEAdvertising.h>
#include <esp_bt.h> // 送信出力設定用


// カラーセンサのインスタンス
Adafruit_TCS34725 tcs = Adafruit_TCS34725();


// BLE関連の定義
#define SERVICE_UUID_16BIT 0xFCBE
BLEAdvertising *pAdvertising = nullptr;


// 最後にセンサを読み取った時刻
unsigned long lastReadTime = 0;


/**
 * @brief BLEアドバタイズデータを更新する
 * @param r Red値 (16bit)
 * @param g Green値 (16bit)
 * @param b Blue値 (16bit)
 * @param c Clear値 (16bit)
 */
void updateAdvertisement(uint16_t r, uint16_t g, uint16_t b, uint16_t c) {
  if (pAdvertising == nullptr) return;


  // アドバタイズを一度停止
  pAdvertising->stop();


  // アドバタイズデータを作成
  BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();


  // 1) AD Structure: Flags
  oAdvertisementData.setFlags(0x06);


  // 2) AD Structure: Service Data - 16-bit UUID
  // ペイロードを構築: [Schema][ID][DataType][Data][SensorData]...
  uint8_t payload[20];
  // --- オープンセンサーサービス ヘッダー部 ---
  payload[0] = 0x01; // Data Schema Version (Fixed to 0x01)
  payload[1] = 0x00; // Individual Identification Number (Fixed to 0x00000000)
  payload[2] = 0x00;
  payload[3] = 0x00;
  payload[4] = 0x00;
  
  // --- データ部1: バージョン情報 ---
  payload[5] = 0x30; // Data Type: Version (0x30)
  payload[6] = 0x00; // Version Number (0x0001)
  payload[7] = 0x01;


  // --- データ部2: カラーセンサー値 ---
  // Red
  payload[8] = 0x52; // Sensor Data Type: Color
  payload[9] = (r >> 8) & 0xFF; // R (Big-Endian)
  payload[10] = r & 0xFF;


  // Green
  payload[11] = 0x52; // Sensor Data Type: Color
  payload[12] = (g >> 8) & 0xFF; // G (Big-Endian)
  payload[13] = g & 0xFF;


  // Blue
  payload[14] = 0x52; // Sensor Data Type: Color
  payload[15] = (b >> 8) & 0xFF; // B (Big-Endian)
  payload[16] = b & 0xFF;


  // Clear
  payload[17] = 0x52; // Sensor Data Type: Color
  payload[18] = (c >> 8) & 0xFF; // C (Big-Endian)
  payload[19] = c & 0xFF;


  // ★修正点: std::stringではなく、ArduinoのString型でデータを渡す
  oAdvertisementData.setServiceData(BLEUUID((uint16_t)SERVICE_UUID_16BIT), String((char*)payload, sizeof(payload)));


  // アドバタイズデータをセット
  pAdvertising->setAdvertisementData(oAdvertisementData);


  // アドバタイズを再開
  pAdvertising->start();
  Serial.println("Advertisement data updated.");
}


/**
 * @brief BLEの初期設定
 */
void setupBLE() {
  BLEDevice::init("M5-ColorSensor");


  // 送信出力を最大(+9dBm)に設定
  esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9);


  pAdvertising = BLEDevice::getAdvertising();
  // アドバタイズ種別を接続不可に設定
  pAdvertising->setAdvertisementType(ADV_TYPE_NONCONN_IND);


  // アドバタイズ周期を100msに設定 (160 * 0.625ms = 100ms)
  pAdvertising->setMinInterval(160);
  pAdvertising->setMaxInterval(160);


  // 初回のアドバタイズを開始(データは後で更新)
  updateAdvertisement(0, 0, 0, 0);
  Serial.println("BLE Advertising started.");
}




/**
 * @brief 初期設定
 */
void setup() {
  M5.begin();
  Serial.begin(115200);


  M5.Display.setTextSize(2);
  M5.Display.println("Color Sensor (TCS3472) Test");


  // カラーセンサの初期化
  if (tcs.begin()) {
    Serial.println("Found color sensor");
    M5.Display.println("Found color sensor");
  } else {
    Serial.println("No TCS3472 found ... check connections");
    M5.Display.println("No TCS3472 found...");
    while (1);
  }


  delay(1000);
  M5.Display.fillScreen(BLACK);


  // BLEの初期設定とアドバタイズ開始
  setupBLE();
}


/**
 * @brief メインループ
 */
void loop() {
  // 1秒ごとに処理を実行
  if (millis() - lastReadTime > 1000) {
    lastReadTime = millis();


    uint16_t r, g, b, c;
    tcs.getRawData(&r, &g, &b, &c);


    // BLEアドバタイズデータを更新
    updateAdvertisement(r, g, b, c);


    // -----------------------------------------------------
    // ディスプレイ表示処理
    // -----------------------------------------------------
    if (c == 0) {
      return;
    }
    uint8_t red   = (uint8_t)min(255.0f, ((float)r / c) * 255.0f);
    uint8_t green = (uint8_t)min(255.0f, ((float)g / c) * 255.0f);
    uint8_t blue  = (uint8_t)min(255.0f, ((float)b / c) * 255.0f);


    M5.Display.fillScreen(M5.Display.color888(red, green, blue));


    M5.Display.fillRoundRect(10, 10, 300, 130, 8, BLACK);
    M5.Display.setTextColor(WHITE);
    M5.Display.setTextSize(2);
    
    M5.Display.setCursor(20, 20);
    M5.Display.printf("Clear: %u\n", c);
    M5.Display.setCursor(20, 50);
    M5.Display.printf("Red:   %u\n", r);
    M5.Display.setCursor(20, 80);
    M5.Display.printf("Green: %u\n", g);
    M5.Display.setCursor(20, 110);
    M5.Display.printf("Blue:  %u\n", b);
  }
}

コンパイルが無事に終了しM5CoreS3 SEに書き込まれ、起動するとアドバタイズデータにカラーセンサー値が載って発信されていることを確認しました!

プロンプト指示(M5Core Grayをオブザーバーにする)

「アドバタイズ発信が出来た!」で終わっては芸がありません。もう1台あるM5Core Grayをオブザーバーとして活用することにします。

M5CoreS3 SEのアドバタイズデータをキャッチして、M5Core Grayのディスプレイの色表示を変えることにします。

これが可能になると、「積層信号灯を目視できない位置にいても、M5Core Grayのディスプレイで確認可能となる」ということになります。ようやくIoTっぽくなってきました!

では、さらに手元にあるM5Core Grayを用いて、M5CoreS3 SEがアドバタイズ発信している内容を受信し、M5CoreS3 SEのディスプレイ表示と同じような表示を行いたい。

仕様を以下に示す。 
仕様を満たすプログラムコードを提示してほしい。

開発環境 
 ・Arduino IDE 

 目的 
 ・先ほどのM5CoreS3 SEが発信しているアドバタイズデータを受信し、自身のディスプレイ表示に反映させる

機能 
 1.アドバタイズデータ取得 
 ・Bluetooth LEのアドバタイズをスキャンする 
 ・アドバタイズデータを以下の条件でフィルタリングする

 (株)ムセンコネクトが提唱しているオープンセンサーサービスを利用していること
 https://www.musen-connect.co.jp/blog/course/product/howto-16bituuid-ble-beacon-open-sensor-service/

 オープンセンサーサービスの仕様書です。
 必要に応じて確認してください。
 https://www.musen-connect.co.jp/dl/open_sensor_beacon_spec.pdf

 AD Type:0x16(Service Data - 16-bit UUID)
 16bit UUID:0xFCBE(リトルエンディアンで指定が必要な場合は注意すること)
 オープンセンサーサービスのヘッダー部の個体識別番号が0x00000000であること
 カラーセンサー値としてデータ種別0x52が4つ続き、ペイロードが20byteであること


 2.ディスプレイ表示 
 ・フィルタリングしたアドバタイズデータのカラーセンサ値に合わせてディスプレイの色を動的に変更する 
 ・Clear、Red、Green、Blueそれぞれの値をディスプレイにテキスト表示する

M5Core Gray(および搭載のESP32)やライブラリが古いなどの影響もあり、数時間コンパイルエラーやデバッグ作業をGemini 2.5proと実施しました。

長らくデバッグ作業をすることで、Gemini 2.5proが導き出した解と、生成されたArduinoスケッチ(プログラムコード)は以下のとおりでした。

お使いの環境(M5Core Grayのハードウェア、ESP32ボードパッケージ、M5Unifiedライブラリ)において、
BLEのバックグラウンド処理とメインループとの間の連携が、根本的に機能していないことを示しています。

この非常に稀で解決が困難な状況を打開するため、これまでのアプローチを全面的に見直し、
タスク間の連携に一切依存しない、全く新しい構造のプログラムとして反映しました。
/**
 * @file M5Core_Gray_OSS_Scanner.ino
 * @author Gemini
 * @brief M5Core Grayでオープンセンサーサービスのアドバタイズデータを受信・表示する
 * @version 5.0
 * @date 2025-08-01
 * @copyright Copyright (c) 2025
 *
 * @note
 * v5.0: 永続的なタスク間通信の問題を解決するため、アーキテクチャを全面的に見直し。
 * BLEコールバックを廃止し、メインループ内で同期スキャンを実行する方式に変更。
 *
 * 機能:
 * 1. オープンセンサーサービス(UUID:0xFCBE)のアドバタイズをスキャンする。
 * 2. 指定された条件(ID, データ種別, ペイロード長)でデータをフィルタリングする。
 * 3. 受信したカラーセンサ値をディスプレイの背景色とテキストで表示する。
 */


#include <M5Unified.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>


// スキャン対象のサービスUUID
#define SERVICE_UUID_16BIT 0xFCBE
#define SCAN_TIME_SECONDS 1 // 1秒ごとにスキャン


BLEScan* pBLEScan;


/**
 * @brief 初期設定
 */
void setup() {
    M5.begin();
    Serial.begin(115200);


    M5.Display.setTextSize(2);
    M5.Display.println("Open Sensor Scanner");


    // BLEの初期化
    BLEDevice::init("");
    pBLEScan = BLEDevice::getScan();
    pBLEScan->setActiveScan(true);
    pBLEScan->setInterval(100);
    pBLEScan->setWindow(99);


    Serial.println("Setup complete. Starting scan loop...");
    M5.Display.println("Scan started...");
    delay(1000);
}


/**
 * @brief メインループ
 */
void loop() {
    Serial.printf("Starting %d second scan...\n", SCAN_TIME_SECONDS);
    
    // 指定した時間だけ同期的にスキャンを実行し、結果を取得する
    BLEScanResults foundDevices = pBLEScan->start(SCAN_TIME_SECONDS, false);
    
    Serial.printf("Scan done! Found %d devices.\n", foundDevices.getCount());


    // 見つかったデバイスを一つずつチェック
    for (int i = 0; i < foundDevices.getCount(); i++) {
        BLEAdvertisedDevice advertisedDevice = foundDevices.getDevice(i);


        // 1. Service Data (UUID: 0xFCBE) が含まれているか確認
        if (advertisedDevice.haveServiceData() && advertisedDevice.getServiceDataUUID().equals(BLEUUID((uint16_t)SERVICE_UUID_16BIT))) {
            
            std::string serviceData = advertisedDevice.getServiceData();


            // 2. ペイロード長が20バイトであるか確認
            if (serviceData.length() != 20) continue;


            const uint8_t* payload = (const uint8_t*)serviceData.c_str();


            // 3. 個体識別番号が0x00000000であるか確認
            if (payload[1] != 0x00 || payload[2] != 0x00 || payload[3] != 0x00 || payload[4] != 0x00) continue;


            // 4. カラーセンサー値(データ種別0x52)が4つ続いているか確認
            if (payload[8] != 0x52 || payload[11] != 0x52 || payload[14] != 0x52 || payload[17] != 0x52) continue;


            // --- 全てのフィルタリング条件をクリア ---
            Serial.println(">>> Found matching device! Processing data...");
            
            // データを抽出
            uint16_t r = (payload[9] << 8) | payload[10];
            uint16_t g = (payload[12] << 8) | payload[13];
            uint16_t b = (payload[15] << 8) | payload[16];
            uint16_t c = (payload[18] << 8) | payload[19];


            // -----------------------------------------------------
            // ディスプレイ表示処理
            // -----------------------------------------------------
            if (c > 0) {
                uint8_t red   = (uint8_t)min(255.0f, ((float)r / c) * 255.0f);
                uint8_t green = (uint8_t)min(255.0f, ((float)g / c) * 255.0f);
                uint8_t blue  = (uint8_t)min(255.0f, ((float)b / c) * 255.0f);
                
                M5.Display.fillScreen(M5.Display.color565(red, green, blue));
            } else {
                M5.Display.fillScreen(BLACK);
            }


            M5.Display.fillRoundRect(10, 10, 300, 130, 8, BLACK);
            M5.Display.setTextColor(WHITE);
            M5.Display.setTextSize(2);
            
            M5.Display.setCursor(20, 20);
            M5.Display.printf("Clear: %u\n", c);
            M5.Display.setCursor(20, 50);
            M5.Display.printf("Red:   %u\n", r);
            M5.Display.setCursor(20, 80);
            M5.Display.printf("Green: %u\n", g);
            M5.Display.setCursor(20, 110);
            M5.Display.printf("Blue:  %u\n", b);


            // 複数の候補が見つかった場合でも、最初の1つだけ処理してループを抜ける
            break; 
        }
    }


    // スキャン結果をクリアして次のスキャンに備える
    pBLEScan->clearResults();


    // M5Unifiedではボタン状態の更新などに必要
    M5.update();
}

動作を確認する②

M5Core Grayにも書き込みが出来たので動作を確認していきます。センサー単体の確認をした時と同じく、積層信号灯を点灯させてセンシングしてみます。

左側がM5CoreS3 SE+カラーセンサー、右側がM5Core Grayです。

M5CoreS3 SEとM5Core Grayのディスプレイ色の階調が異なるため、まったく同じ色味にはなりませんでしたがアドバタイズデータのオープンセンササービスに載せた値は正しく受信できているようでした。

動画:m5stack-blog6-1.mp4

最大2秒程度タイムラグがあって、M5Core Grayに反映されるような動きでした。即応性は求めていないので問題ありません。やりたいことは達成できました!

まとめ

もともと想定していたのは、タイトルにあるとおりM5Stackを使って「センサデータをアドバタイズ発信」することでしたが、それに加えて「アドバタイズ受信」をすることができました。結果的にブロードキャスターとオブザーバー、どちらの動作も体験することができました。

また、ムセンコネクトが提唱する「オープンセンササービス ビーコン仕様 」を活用することで、今回のシステムのためにイチから仕様を考える必要なく、気軽にアドバタイズデータの発信ができました。

Bluetoothの通信は用途によりプロファイルやサービス、キャラクタリスティックが規定されているものが多いですが、アドバタイズデータはセンサー値などの規定がありません。我々の考えや、公開の経緯はこちらの記事に載っていますのでぜひ併せてご覧ください。

今回は、カラーセンサーの動作確認をきっかけに、「工場などに設置されている積層信号灯を後付けでIoT化する手順」の具体例を示せたものと考えています。

しかし、冒頭にもお伝えしましたが、M5Stack自体はBluetooth認証を取得していないため、M5Stackで製品を開発しようとすると、最後のBluetooth認証取得の際に困難が待ち構えています。M5Stackを用いた製品化をご検討の際は、この点に十分ご注意ください。

次回は、異なるセンサーをつかったIoT化にチャレンジしたいと思います。お楽しみに!

よろしければシェアをお願いします
目次