3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VisionProでBluetoothLowEnergy(BLE)を用いた自作デバイスを利用する方法

Last updated at Posted at 2025-12-10

この記事は、Unity Advent Calendar 2025 11日目の記事です。

自己紹介

Graffity株式会社でUnityエンジニアをしていますsadaです。

まえがき

VisionProでは基本的にはハンドジェスチャー・ゲームパッド、一部の空間アクセサリを用いて操作することができます。
自作のデバイスを接続してVisionProで利用しようとするとBLEなどの無線を用いて繋ぐ必要があります。

BLEとはBluetoothLowEnergyのことでBluetoothの規格の一種で、主にIoT端末で使用される規格です。

今回BLEを用いてVisionPro上で動作する自作デバイスを作ってみました。

事前調査

そもそもVisionOSでBLEが使えるのか?

CoreBluetooth(BLEのフレームワーク)自体はVisionOSで利用可能です。

貼り付けた画像_2025_12_07_22_16.png

ただし、利用可能な形式がCentralというモードのみ対応となっています。

In watchOS, tvOS, and visionOS, you can’t advertise services using a CBPeripheralManager object because support for doing so is unavailable.
https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager より引用

BLEのモードについて

BLEには主に2つの動作モードがあります。

Centralモード

主にデータを取得・利用する側に用いられます。
主な利用例としてはスマホやPCなどの通信機器に用いられます。
他のデバイスをスキャンを行い接続やデータのやりとりなどをを行います。

Peripheralモード

主にデータを提供する側に用いられます。
主な利用例としてはセンサーデバイスなどに用いられます。
Centralデバイスからのスキャンや接続リクエストに応答して必要なデータを提供します。

今回自作デバイスをPeripheral、VisionProをCentralとして使う想定であるため問題なく利用ができます。

Unityで使えるプラグインはあるのか?

Unityで利用可能なBLEのプラグインはいくつか存在します。
アセットストアで提供されているものや、

github上で公開されているOSSもいくつか存在します。

ただ、どれもVisionOSには対応しておらず、またPeripheralの機能も含まれていることからそのままでは動作しません。
上記のOSSをフォークしてVIsionOSで利用可能なものに修正するのも一つの手ではありますが、今回はCoreBluetoothのCentralの機能のみを用いるネイティブプラグインを作成することにしました。

実装

実装環境

項目 Version等
Unity 6000.0.62f1
Xcode 26.1.1
PolySpatial 2.4.3

ネイティブプラグイン実装

今回CoreBluetoothのCentralの機能のみを外出しするようなネイティブプラグインを作成しました。

作成したネイティブプラグインのコード・Unity側のネイティブプラグインを呼び出すコード・サンプル実装は以下のgithubで公開していますので参考にしてください。

Unity側実装

作成した.hファイルと.mmファイルをPlugins/VisonOS以下に配置し、プラグインを利用するスクリプトを追加してビルドするだけではBluetoothの利用はできません。Cameraやマイクの利用確認と同じように使用許諾の実装を入れる必要があります。

また、CoreBluetoothのライブラリもそのままでは利用できないので利用するように設定を変える必要があります。
どちらもUnityでXcodeProjectのビルド後Xcode上でInfo.plistやFrameworkの追加をすればよいのですが、以下のようにPostProcessBuildでビルド成果物を書き換える手法の方を用いると自動化できて便利です。

BlePostprocess.cs
using System.IO;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Callbacks;
# endif
#if UNITY_VISIONOS && UNITY_EDITOR
using UnityEditor.iOS.Xcode;
#endif

namespace VisionOS.BLE.Editor
{
    /// <summary>
    /// visionOSビルド後にBluetooth利用説明とCoreBluetooth.framework追加を行うポストプロセス。
    /// </summary>
    public static class BlePostprocess
    {
#if UNITY_VISIONOS && UNITY_EDITOR
        private const string UsageDescription = "used to communicate with Bluetooth devices.";

        [PostProcessBuild(900)]
        public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject)
        {
            if (target != BuildTarget.VisionOS)
            {
                return;
            }

            AddUsageDescription(pathToBuiltProject);
            AddBluetoothFramework(pathToBuiltProject);
        }

        private static void AddUsageDescription(string pathToBuiltProject)
        {
            var plistPath = Path.Combine(pathToBuiltProject, "Info.plist");
            var plist = new PlistDocument();
            plist.ReadFromFile(plistPath);
            plist.root.SetString("NSBluetoothAlwaysUsageDescription", UsageDescription);
            plist.WriteToFile(plistPath);
        }

        private static void AddBluetoothFramework(string pathToBuiltProject)
        {
            var projPath = PBXProject.GetPBXProjectPath(pathToBuiltProject);
            var proj = new PBXProject();
            proj.ReadFromFile(projPath);

            var targetGuid = proj.GetUnityMainTargetGuid();
            proj.AddFrameworkToProject(targetGuid, "CoreBluetooth.framework", /*weak*/ false);
            proj.WriteToFile(projPath);
        }
#endif
    }
}

動作

使用する自作デバイス

今回自作デバイスとしてSeeed Studio XIAO ESP32C6というマイコンを使用します。ブレッドボード上にマイコンとLED、スイッチを配置しただけの簡易なデバイスです。
IMG_0207.jpeg

デモ実装として以下のような簡単な動作を行うように実装しました。

  • BLE Peripheralとして動作
  • ペアリングされたら0,1,2のコマンドを受け付けて動作を行う
    • 0:LED消灯
    • 1:LED点灯
    • 2:LED点滅
  • ボタンを押すことでBLE通信を行いCentral端末へPINGの文字列を送信
実装コード
main.cpp
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

constexpr uint8_t BUTTON_PIN = D5;
constexpr uint8_t LED_PIN    = D6;

constexpr uint32_t DEBOUNCE_MS        = 50;
constexpr uint32_t BLINK_INTERVAL_MS  = 500;

// UUID
static const char *SERVICE_UUID = "12345678-1234-5678-1234-56789abcdef0";
static const char *CHAR_UUID    = "12345678-1234-5678-1234-56789abcdef1";

enum class LedMode { Off = 0, On = 1, Blink = 2 };

volatile bool     g_button_event   = false;
volatile uint32_t g_last_isr_ms    = 0;
LedMode           g_mode           = LedMode::Blink;
int               g_led_state      = LOW;
uint32_t          g_last_blink_ms  = 0;
BLECharacteristic *g_char          = nullptr;

// 割り込みハンドラ
void IRAM_ATTR onButton() {
    uint32_t now = millis();
    if (now - g_last_isr_ms < DEBOUNCE_MS) return;
    g_last_isr_ms = now;
    g_button_event = true;
}

void applyMode() {
    switch (g_mode) {
        case LedMode::On:
            g_led_state = HIGH;
            break;
        case LedMode::Off:
            g_led_state = LOW;
            break;
        case LedMode::Blink:
        default:
            g_led_state = LOW;
            g_last_blink_ms = millis();
            break;
    }
    digitalWrite(LED_PIN, g_led_state);
}


class LedCharCallbacks : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) override {
        String val = pCharacteristic->getValue();
        if (val.length() == 0) return;

        char c = val[0];
        switch (c) {
            case '0': g_mode = LedMode::Off;   break;
            case '1': g_mode = LedMode::On;    break;
            case '2': g_mode = LedMode::Blink; break;
            default:  return;
        }
        applyMode();
    }
};

void setup() {
    pinMode(BUTTON_PIN, INPUT_PULLUP);
    pinMode(LED_PIN, OUTPUT);
    applyMode();

    attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), onButton, FALLING);

    // BLE初期化
    BLEDevice::init("XIAO-LED");
    BLEServer *server = BLEDevice::createServer();

    BLEService *service = server->createService(SERVICE_UUID);

    g_char = service->createCharacteristic(
        CHAR_UUID,
        BLECharacteristic::PROPERTY_READ  |
        BLECharacteristic::PROPERTY_WRITE |
        BLECharacteristic::PROPERTY_NOTIFY
    );

    g_char->setCallbacks(new LedCharCallbacks());
    g_char->setValue("2");  // 初期モード: Blink

    service->start();

    BLEAdvertising *adv = BLEDevice::getAdvertising();
    adv->addServiceUUID(SERVICE_UUID);
    adv->setScanResponse(true);
    adv->setMinPreferred(0x06);
    adv->setMaxPreferred(0x12);
    BLEDevice::startAdvertising();
}

void loop() {
    // ボタンイベントが来たらPINGを送信
    if (g_button_event) {
        g_button_event = false;
        if (g_char) {
            g_char->setValue("PING");
            g_char->notify();
        }
    }

    // Blinkモードのときだけ点滅処理
    if (g_mode == LedMode::Blink) {
        uint32_t now = millis();
        if (now - g_last_blink_ms >= BLINK_INTERVAL_MS) {
            g_last_blink_ms = now;
            g_led_state = (g_led_state == LOW) ? HIGH : LOW;
            digitalWrite(LED_PIN, g_led_state);
        }
    }

    delay(5);
}

動作している様子

このように、VisionProから自作デバイス、自作デバイスからVisionProへの双方向通信ができました。

まとめ

VisionOSではBLEの利用は可能です。ただし、Centralの機能のみが利用可能で、Peripheralとしての機能は利用が不可となっています。
UnityでBLEを利用するためのプラグインはいくつか存在しますが、どれもVisionOSには対応していません。

既存のOSSをフォークするか、自作でCentralのみの機能に絞ったネイティブプラグインを作成することで、VisionOSでBLEが利用可能になります。

今回はデモとして簡易なデバイスを用いましたが、この手法を流用することで自作のコントローラデバイスをVisionProに接続したり、様々なセンサデバイスをVisionProから利用することが可能になります。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?